C++0x 右值引用模板参数推导

Posted

技术标签:

【中文标题】C++0x 右值引用模板参数推导【英文标题】:C++0x rvalue reference template argument deduction 【发布时间】:2010-10-27 13:49:15 【问题描述】:

鉴于GMan's 美味邪恶auto_cast 实用函数炮制here,我一直试图弄清楚为什么当我试图从右值auto_cast 时它不能为我编译(在MSVC 上10.0)。

这是我正在使用的代码:

template <typename T>
class auto_cast_wrapper : boost::noncopyable

  public:
    template <typename R>
    friend auto_cast_wrapper<R> auto_cast(R&& pX);

    template <typename U>
    operator U() const
    
      return static_cast<U>( std::forward<T>(mX) );
    

  private:
    //error C2440: 'initializing': cannot convert from 'float' to 'float &&'
    auto_cast_wrapper(T&& pX) : mX(pX)  

    T&& mX;
;

template <typename R>
auto_cast_wrapper<R> auto_cast(R&& pX)

  return auto_cast_wrapper<R>( std::forward<R>(pX) );


int main()

  int c = auto_cast( 5.0f );  // from an rvalue

尽我所能,我尝试遵循 C++0x 引用折叠规则和here 概述的模板参数推导规则,据我所知,上面给出的代码应该可以工作。

回想一下,在 0x 之前的 C++ 中,不允许对引用进行引用:像 A& & 之类的东西会导致编译错误。相比之下,C++0x 引入了以下引用折叠规则:

A&&变成A& A& && 变成 A& A&& & 变成 A& A&& && 变为 A&&

第二条规则是一个特殊的模板参数推导规则,用于通过对模板参数的右值引用获取参数的函数模板:

template<typename T>  
void foo(T&&);

这里适用以下规则:

    当在 A 类型的左值上调用 foo 时,T 解析为 A&,因此,根据上面的引用折叠规则,参数类型实际上变为 A&。 当在 A 类型的右值上调用 foo 时,T 解析为 A,因此参数类型变为 A&&。

现在,当我将鼠标悬停在对auto_cast( 5.0f ) 的调用上时,工具提示正确地将其返回值显示为auto_cast_wrapper&lt;float&gt;。这意味着编译器正确地遵循了规则 2:

当在 A 类型的右值上调用 foo 时,T 会解析为 A。

既然我们有一个auto_cast_wrapper&lt;float&gt;,构造函数应该实例化一个float&amp;&amp;。但是错误消息似乎暗示它实例化了一个float的值。

这是完整的错误消息,再次显示 T=float 正确但 T&& 参数变为 T?

 main.cpp(17): error C2440: 'initializing' : cannot convert from 'float' to 'float &&'
     You cannot bind an lvalue to an rvalue reference
     main.cpp(17) : while compiling class template member function 'auto_cast_wrapper<T>::auto_cast_wrapper(T &&)'
     with
     [
         T=float
     ]
     main.cpp(33) : see reference to class template instantiation 'auto_cast_wrapper<T>' being compiled
     with
     [
         T=float
     ]

有什么想法吗?

【问题讨论】:

你有没有机会把问题集中一点?与其将编辑内容作为单独的段落放在底部,不如将它们编辑问题本身。作为读者,我不需要知道您将内容附加到问题的顺序,我只想阅读问题的最佳版本。如果您想自己发布答案,请将其作为实际答案,而不是作为问题底部的另一段。事实上,如果我什至想知道这个问题是关于什么的,我就必须阅读大量的文本和代码。 @jalf:是的,对不起,jalf。我不想创建单独的答案,因为 DeadMG 已经正确回答了。我想我只是认为我这样做是不礼貌的,尽管我认为对它的一些澄清不会让其他人在未来读到这篇文章。额外的问题可能应该是一个完全独立的问题,因为它与原始问题没有真正的关系。 在 SO 上,对已经回答的问题发布新的、更好的答案并没有错。如果您对此感觉不好,请接受 DeadMG 的回答,但在其下方发布您自己的回答。 :) @jalf:好的,我创建了一个单独的答案并将其恢复为原始问题。还是有点长,但说明了我的谬误思路。 【参考方案1】:

您忘记将 std::forward T&& 参数传递给 auto_cast_wrapper 构造函数。这打破了转发链。编译器现在发出警告,但它似乎工作正常。

template <typename T>
class auto_cast_wrapper

  public:
    template <typename R>
    friend auto_cast_wrapper<R> auto_cast(R&& pX);

    template <typename U>
    operator U() const
    
      return static_cast<U>( std::forward<T>(mX) );
    

  private:
    //error C2440: 'initializing': cannot convert from 'float' to 'float &&'
    auto_cast_wrapper(T&& pX) : mX(std::forward<T>(pX))  

    auto_cast_wrapper(const auto_cast_wrapper&);
    auto_cast_wrapper& operator=(const auto_cast_wrapper&);

    T&& mX;
;

template <typename R>
auto_cast_wrapper<R> auto_cast(R&& pX)

  return auto_cast_wrapper<R>( std::forward<R>(pX) );


float func() 
    return 5.0f;


int main()


  int c = auto_cast( func() );  // from an rvalue
  int cvar = auto_cast( 5.0f );

  std::cout << c << "\n" << cvar << "\n";
  std::cin.get();

打印一对五。

【讨论】:

那么你的意思是构造函数应该是auto_cast_wrapper(T&amp;&amp; pX) : mX(std::forward&lt;T&gt;(pX))?我认为你是对的,这是有道理的。但现在我认为它表现出未定义的行为。我认为在转换运算符被触发之前,文字超出了范围。所以这似乎无论如何都不能从右值工作? @dvide:我用一些右值对其进行了测试——例如,由函数的值返回——它似乎有效。 @DeadMG:你的警告是什么?在什么编译器上?我收到警告:引用成员被初始化为一个临时的,在构造函数退出后不会持续存在。当我测试它时,c 的值被搞砸了。鉴于它在技术上未定义,它可能只是看起来有效? @DeadMG:正如我所说,我也在 MSVC10 上。我相信警告是正确的,不应该因为它似乎在您的计算机上工作而被忽略。完全相同的代码在调试模式下为我打印一对垃圾数字,在发布模式下为我打印一对五。 我会接受这一点,因为它确实回答了原始问题,但我已经扩展了我的问题,并就这种潜在的未定义行为进行了跟进。【参考方案2】:

很抱歉发布未经测试的代码。 :)

DeadMG 是正确的,该参数也应该被转发。我相信警告是错误的,MSVC 有一个错误。从电话中考虑:

auto_cast(T()); // where T is some type

T() 将持续到完整表达式的末尾,这意味着 auto_cast 函数、auto_cast_wrapper 的构造函数和用户定义的转换都引用了一个仍然有效的对象。

(由于包装器除了转换或破坏之外不能做任何事情,它不能比传递给auto_cast的值长。)

我修复的可能是让成员只是一个T。不过,您将进行复制/移动,而不是直接投射原始对象。但也许随着编译器优化它消失了。


不,转发不是多余的。它维护了我们自动转换的值类别:

struct foo

    foo(int&)  /* lvalue */ 
    foo(int&&)  /* rvalue */ 
;

int x = 5;
foo f = auto_cast(x); // lvalue
foo g = auto_cast(7); // rvalue

如果我没记错的话,转换运算符不应该(当然不需要)标记为const

【讨论】:

感谢 GMan。我不知道它应该持续到整个表达式的结尾。这就说得通了。我想这只是一个错误。并感谢完美的转发澄清。这很酷。 @dvide:没有问题。不要承诺 100% 这是一个错误,但我很确定。它仍然只是一个参考(如const&amp; 版本),只能是左值或右值,所以我认为它应该是相同的。感谢您查看。 是的,我对两个版本之间临时性的终生不一致感到有些困惑。据我所知,它们应该是相同的,但我仍然习惯于对引用进行右值,所以我非常谨慎地避免得出任何结论 =)【参考方案3】:

它不编译的原因与它不编译的原因相同:

float rvalue()  return 5.0f 

float&& a = rvalue();
float&& b = a; // error C2440: 'initializing' : cannot convert from 'float' to 'float &&'  

由于a 本身是一个左值,它不能绑定到b。在auto_cast_wrapper 构造函数中,我们应该再次在参数上使用std::forward&lt;T&gt; 来解决这个问题。请注意,我们可以在上面的特定示例中只使用std::move(a),但这不会涵盖也应该与左值一起使用的通用代码。所以auto_cast_wrapper 构造函数现在变成了:

template <typename T>
class auto_cast_wrapper : boost::noncopyable

  public:
    ...

  private:
    auto_cast_wrapper(T&& pX) : mX( std::forward<T>(pX) )  

    T&& mX;
;

不幸的是,现在这似乎表现出未定义的行为。我收到以下警告:

警告 C4413: 'auto_cast_wrapper::mX' : 引用成员被初始化为在构造函数退出后不会持续存在的临时对象

在触发转换运算符之前,文字似乎超出了范围。虽然这可能只是 MSVC 10.0 的编译器错误。从 GMan's answer 开始,临时的生命周期应该一直持续到完整表达式的结尾。

【讨论】:

以上是关于C++0x 右值引用模板参数推导的主要内容,如果未能解决你的问题,请参考以下文章

C++进阶第二十五篇——C++11(列表初始化+变量类型推导+右值引用和移动语义+新的类功能+可变模板参数)

C++进阶第二十五篇——C++11(列表初始化+变量类型推导+右值引用和移动语义+新的类功能+可变模板参数)

C++进阶第二十五篇——C++11(列表初始化+变量类型推导+右值引用和移动语义+新的类功能+可变模板参数)

模板参数的“右值引用”是转发引用

c++11-17 模板核心知识—— 理解模板参数推导规则

c ++使用临时对象右值初始化左值引用以及自动推导[重复]