Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected return type when combining std::forward, std::move and volatile

Code on gcc.godbolt.org.

I created a simple type trait to remove rvalue references:

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;

I'm using it to implent a copy_if_rvalue(x) function whose return type depends on the passed argument:

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);
}

To make sure the function returns the correct types, I've written some simple static assertions:

// 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&

All the above static assertions compile successfully. When trying to std::move a volatile int variable, though, something unexpected occurs:

// 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
>{});

Assertion (0) fails - the volatile does not propagate, as demonstrated by assertion (1).

However, assertion (2) passes, even if I think it should be equivalent to assertion (0), since copy_if_rvalue's return type is exactly the same as (2)'s first type:

// (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))>

It seems that volatile is not propagated only when std::move is used and only through the copy_if_rvalue template function.

What is going on here?

like image 416
Vittorio Romeo Avatar asked Apr 27 '16 14:04

Vittorio Romeo


1 Answers

There are no cv-qualified scalar prvalues. [expr]/6:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

I.e. the same rule giving

int const f();
f() // <=

type int applies here as well. If you try some class type instead of int (e.g. std::string), you will get the expected types.

like image 178
Columbo Avatar answered Oct 25 '22 05:10

Columbo