将引用显式传递到不需要引用的函数模板中会导致问题吗?

Can it cause problems to explicitly pass a reference into a function template that's not expecting one?

本文关键字:引用 问题 函数模板 不需要      更新时间:2023-10-16

有一个关于传递下列函数模板的问题,并且用引用字符串参数实例化它:

template <typename T>
void foo(T t) {}
答案当然是显式地给出实参:
int main() {
   std::string str("some huge text");
   foo<std::string&>(str);
}

(顺便说一句,有一个关于使用演绎和传递c++ 0x的std::ref(str)的建议,但这需要在函数内部使用.get(), OP的要求是透明的。)

然而,在我看来,毫无疑问,函数模板的作者想让实参通过value传递,否则他会写:
template <typename T>
void foo(T& t) {}

(有可能他有意让这两种情况都成为可能,但出于某种原因,我觉得这似乎不太可能)

  • 是否有任何"合理"的情况下,传递引用到foo<T>,其中foo的作者有意图函数总是取其值的参数,可能会导致问题?

  • 我正在寻找的答案最有可能由foo的函数体组成,其中T的引用类型的使用与T的值类型的使用相比会导致意外/不希望的语义。

考虑这样写算法是很常见的:

template <typename InputIterator>
void dosomething(InputIterator first, InputIterator last, otherparams) {
    while (first != last) {
        do the work;
        ++first;
    }
}

这也是标准算法在GCC和旧SGI STL中实现的方式。

如果first是被引用的,那么它将被修改,所以肯定有很多模板函数会因为引用模板参数而"出错"。在本例中,将first修改为last。在另一个实现中,或者在下一个版本中,它可能被复制而根本不修改。这就是"意想不到的/不希望的语义"。

我不确定你是否称这为"问题"。这不是函数的作者想要的行为,并且可能没有记录如果通过引用传递first会发生什么(它不是针对标准算法)。

对于标准算法,我认为是UB,所以调用者有错。标准规定模板实参应该是一个迭代器,虽然T*或某些库迭代器是迭代器类型,但T* &或reference-to-library-iterator不是。

只要模板函数的作者清楚地记录了他们的模板实参的需求,我怀疑通常就会以与标准算法和迭代器相同的方式出现——引用类型不是有效的模板实参,因此调用者有错。在需求非常简单的情况下(具有指定行为的两个表达式),引用类型可能不被排除,但是只要函数没有说它修改参数,也没有说如何修改参数,调用者应该考虑,因为没有记录参数如何修改,那么它是未指定的。如果他们使用引用类型调用函数,并且对参数是否被修改或如何被修改感到惊讶,那么这也是调用者的错误。

我希望文档不充分的函数在出错时可能会有争议,因为有时它依赖于相当仔细的阅读。

如果程序员决定偷懒,并使用输入参数作为临时变量来计算输出,那么您将得到有问题的结果:

template <class T>
T power2n(T a, int n) {
    if (n == 0) return 1;
    for (int i = 0 ; i < n; i++) 
    {
       a *= a; 
    }
    return a;
}

现在如果我通过引用传递第一个形参,它的值就乱了。

不是说这是好的编程,只是说它发生了。