【问题标题】:Preventing non-const lvalues from resolving to rvalue reference instead of const lvalue reference防止非常量左值解析为右值引用而不是 const 左值引用
【发布时间】:2011-12-06 13:55:38
【问题描述】:

我在重载函数以通过 const 引用获取值时遇到问题,或者,如果它是一个右值,一个右值引用。问题是我的非常量左值绑定到函数的右值版本。我在 VC2010 中这样做。

#include <iostream>
#include <vector>

using namespace std;

template <class T>
void foo(const T& t)
{cout << "void foo(const T&)" << endl;}

template <class T>
void foo(T&& t)
{cout << "void foo(T&&)" << endl;}

int main()
{
    vector<int> x;
    foo(x); // void foo(T&&) ?????
    foo(vector<int>()); // void foo(T&&)
}

优先级似乎是推断 foo(x) 为

foo< vector<int> & >(vector<int>& && t)

而不是

foo< vector<int> >(const vector<int>& t)

我尝试将右值引用版本替换为

void foo(typename remove_reference<T>::type&& t)

但这只会导致所有内容都解析为 const-lvalue 参考版本。

如何防止这种行为?为什么这是默认值 - 考虑到允许修改右值引用,这似乎很危险,这给我留下了一个意外修改的局部变量。

编辑:刚刚添加了函数的非模板版本,它们按预期工作。使函数成为模板会更改重载解析规则?那……真是令人沮丧!

void bar(const vector<int>& t)
{cout << "void bar(const vector<int>&)" << endl;}

void bar(vector<int>&& t)
{cout << "void bar(vector<int>&&)" << endl;}

bar(x); // void bar(const vector<int>&)
bar(vector<int>()); // void bar(vector<int>&&)

【问题讨论】:

  • 当你使x const 时会发生什么?在另一个调用 foo 之后使用 x 会发生什么?
  • 可以安排推导的 T&& 只绑定到右值。见ideone.com/XSBifq。但我不会推荐这个。公认的答案,即不要像这样使用重载,是正确的方法。

标签: c++ templates metaprogramming rvalue-reference


【解决方案1】:

当你有一个这样的模板化函数时,你几乎从不想要重载。 T&amp;&amp; 参数是 catch anything 参数。你可以用它来获得任何你想要摆脱one重载的行为。

#include <iostream>
#include <vector>

using namespace std;

template <class T>
void display()
{
    typedef typename remove_reference<T>::type Tr;
    typedef typename remove_cv<Tr>::type Trcv;
    if (is_const<Tr>::value)
        cout << "const ";
    if (is_volatile<Tr>::value)
        cout << "volatile ";
    std::cout << typeid(Trcv).name();
    if (is_lvalue_reference<T>::value)
        std::cout << '&';
    else if (is_rvalue_reference<T>::value)
        std::cout << "&&";
    std::cout << '\n';
}

template <class T>
void foo(T&& t)
{
    display<T>();
}

int main()
{
    vector<int> x;
    vector<int> const cx;
    foo(x); // vector<int>&
    foo(vector<int>()); // vector<int>
    foo(cx);  // const vector<int>&
}

【讨论】:

  • 是的,你明白了。虽然我的措辞不同:&amp;&amp; 在非模板函数中表示“移动语义”,&amp;&amp; 在模板函数中表示“完美转发”
  • 我想这很公平,但这确实让我有点沮丧。我可以看到由于语义之间缺乏统一性而引起的问题。
  • 很公平。我和其他一些人(彼得和戴夫)试图通过引入右值引用来用一块石头杀死两只鸟:移动语义和完美转发。当时(2002 年)我们认为,对语言进行最小的更改,几乎没有或没有向后不兼容,并完成多个不可能完成的任务,这将是一件好事。但是,如果我们一直在进行设计,那么更清洁的解决方案似乎是合理的。当然,如果这种挫败感增加,应该将自己限制在 C++98/03 子集,这样就不会出现这个问题。
  • 向丹尼斯·里奇的家人致以最诚挚的慰问。
  • 嗯,是的,但这是一个非常糟糕的选择。这只是意味着您必须记住,在尝试使用右值引用对任何函数进行模板化时需要非常小心。不幸的是,如果您忘记了它通常仍然可以编译,除非您将杀死所有调用该函数的对象!我可以很容易地看到这会导致微妙且难以发现的错误,尤其是如果您习惯了 总是 生成等效函数的 C++03 模板。
【解决方案2】:

为了使T&amp;&amp; 绑定到左值引用,T 本身必须是左值引用类型。可以禁止模板被引用类型T实例化:

template <typename T>
typename std::enable_if<!std::is_reference<T>::value>::type foo(T&& t)
{
    cout << "void foo(T&&)" << endl;
}

enable_if&lt;utility&gt; 中找到; is_reference 位于 &lt;type_traits&gt;

采用T&amp;&amp; 的重载优先于采用T const&amp; 的重载的原因是T&amp;&amp; 是完全匹配(与T = vector&lt;int&gt;&amp;)但T const&amp; 需要进行限定转换(const-qualification 必须被添加)。

这只发生在模板上。如果您有一个采用std::vector&lt;int&gt;&amp;&amp; 的非模板函数,您将只能使用右值参数调用该函数。当您有一个采用T&amp;&amp; 的模板时,您不应将其视为“右值引用参数;”它是一个“通用参考参数”(我相信斯科特迈耶斯使用了类似的语言)。它可以接受任何东西。

允许函数模板的T&amp;&amp; 参数绑定到任何类别的参数是实现完美转发的原因。

【讨论】:

  • 所以每当你用右值引用模板化一个函数时,你需要时刻记住手动移除引用重载?
  • @Ayjay 不,通常一个使用完美转发的模板就足够了。由于它接受任何东西(请参阅霍华德的回答)并且保留了参数的值类别和 cv 限定符,因此您很少需要另一个重载。
  • 我一直在尝试这个,我发现的问题是很难解决编译错误。即使将要执行的实际代码在语法上是正确的,但其他一些 if 条件具有无效的语法。这让我很沮丧,我可以使用 enable-if 来恢复非模板右值重载规则,这样我就可以像没有模板化一样编写函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-20
  • 1970-01-01
  • 2017-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多