8

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

这是我正在使用的代码:

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 参考折叠规则和此处概述的模板参数推导规则,据我所知,上面给出的代码应该可以工作。

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

  • A& & 变成 A&
  • A& && 变成 A&
  • A&& & 变成 A&
  • A&& && 变成 A&&

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

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

在这里,以下规则适用:

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

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

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

所以既然我们有一个auto_cast_wrapper<float>,构造函数应该实例化一个float&&。但是错误消息似乎暗示它实例化为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
     ]

有什么想法吗?

4

3 回答 3

4

您忘记将 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();
}

打印一对五。

于 2010-10-27T14:11:57.673 回答
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

于 2010-10-27T18:46:27.607 回答
2

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

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<T>再次使用参数来解决这个问题。请注意,我们可以只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 的回答来看,临时的生命周期应该一直持续到完整表达式的结尾。

于 2010-10-27T20:09:52.307 回答