组合std :: forward,std :: move和volatile时出现意外的返回类型

Vit*_*meo 6 c++ decltype language-lawyer trailing-return-type c++14

上gcc.godbolt.org.

我创建了一个简单的类型特征来删除右值引用:

template <typename T>
struct remove_rvalue_reference { using type = T; };

template <typename T>
struct remove_rvalue_reference<T&&> { using type = T; };

template <typename T>
using remove_rvalue_reference_t = 
    typename remove_rvalue_reference<T>::type;
Run Code Online (Sandbox Code Playgroud)

我用它来实现一个copy_if_rvalue(x)函数,其返回类型取决于传递的参数:

template <typename T>
constexpr auto copy_if_rvalue(T && x) 
  -> remove_rvalue_reference_t<decltype(std::forward<decltype(x)>(x))>
{
    return std::forward<decltype(x)>(x);
}
Run Code Online (Sandbox Code Playgroud)

为了确保函数返回正确的类型,我编写了一些简单的静态断言:

// literal
static_assert(std::is_same<
    decltype(copy_if_rvalue(0)), int
>{});

// lvalue
int lv = 10;
static_assert(std::is_same<
    decltype(copy_if_rvalue(lv)), int&
>{});

// const lvalue
const int clv = 10;
static_assert(std::is_same<
    decltype(copy_if_rvalue(clv)), const int&
>{});

// rvalue
int rv = 10;
static_assert(std::is_same<
    decltype(copy_if_rvalue(std::move(rv))), int
>{});

// volatile lvalue
volatile int vlv = 10;
static_assert(std::is_same<
    decltype(copy_if_rvalue(vlv)), volatile int&
>{});

// const lvalue
volatile const int vclv = 10;
static_assert(std::is_same<
    decltype(copy_if_rvalue(vclv)), volatile const int&
Run Code Online (Sandbox Code Playgroud)

以上所有静态断言都编译成功.当尝试std::move一个volatile int变量,但是,意想不到的事情发生:

// volatile rvalue
volatile int vrv = 10;

// (0) fails:
static_assert(std::is_same<
    decltype(copy_if_rvalue(std::move(vrv))), volatile int
>{});

// (1) unexpectedly passes:
static_assert(std::is_same<
    decltype(copy_if_rvalue(std::move(vrv))), int
>{});

// (2) unexpectedly passes:
static_assert(std::is_same<
    remove_rvalue_reference_t<decltype(std::forward<decltype(vrv)>(std::move(vrv)))>, 
    volatile int
>{});
Run Code Online (Sandbox Code Playgroud)

断言(0)失败 - volatile不会传播,如断言(1)所示.

但是,断言(2)通过,即使我认为它应该等于断言(0),因为copy_if_rvalue返回类型与(2)的第一个类型完全相同:

// (from assertion (2))
remove_rvalue_reference_t<decltype(std::forward<decltype(vrv)>(std::move(vrv)))> 

// ...should be equivalent to...

// (from copy_if_rvalue)
-> remove_rvalue_reference_t<decltype(std::forward<decltype(x)>(x))>
Run Code Online (Sandbox Code Playgroud)

似乎volatile仅在std::move使用时才传播,并且仅通过copy_if_rvalue模板功能传播.

这里发生了什么?

Col*_*mbo 4

没有 cv 限定的标量纯右值。[表达式]/6 :

\n\n
\n

如果纯右值最初的类型为 \xe2\x80\x9c cv T \xe2\x80\x9d,其中T是 cv 未限定的非类、非数组类型,则表达式的类型将调整为T之前的类型任何进一步的分析。

\n
\n\n

即相同的规则给出

\n\n
int const f();\nf() // <=\n
Run Code Online (Sandbox Code Playgroud)\n\n

类型int也适用于此。如果您尝试某种类类型而不是int(例如std::string),您将获得预期的类型。

\n