Given the following code:
#include <string> void foo() { std::string s(std::move("")); }
This compiles with apple clang (xcode 7) and does not with visual studio 2015 which generates the following error:
error C2440: 'return': cannot convert from 'const char [1]' to 'const char (&&)[1]' note: You cannot bind an lvalue to an rvalue reference main.cpp(4): note: see reference to function template instantiation 'const char (&&std::move<const char(&)[1]>(_Ty) noexcept)[1]' being compiled with [ _Ty=const char (&)[1] ]
Ignoring for the moment that the move is redundant, which standard library implementation is the more correct in this case?
My feeling is that the type of ""
is const char[1]
so std::move
should return std::remove_reference<const char[1]&>::type&&
which would be const char[1]&&
.
It seems to me that this should decay to const char*
.
Or do I misunderstand the rules?
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
The compiler scans the source code file, looks for, and stores all occurrences of string literals. It can use a mechanism such as a lookup table to do this. It then runs through the list and assigns the same address to all identical string literals.
std::string literals are Standard Library implementations of user-defined literals (see below) that are represented as "xyz"s (with a s suffix). This kind of string literal produces a temporary object of type std::string , std::wstring , std::u32string , or std::u16string , depending on the prefix that is specified.
std::move in C++Moves the elements in the range [first,last] into the range beginning at result. The value of the elements in the [first,last] is transferred to the elements pointed by result. After the call, the elements in the range [first,last] are left in an unspecified but valid state.
This looks like a Visual Studio bug. This comes down to the std::move and if we look at the cppreference page it has the following signature:
template< class T > typename std::remove_reference<T>::type&& move( T&& t );
and it returns:
static_cast<typename std::remove_reference<T>::type&&>(t)
which matches the draft C++ standard section 20.2.4
forward/move helpers [forward].
Using the code I grabbed from here we can see the following example:
#include <iostream> template<typename T> struct value_category { // Or can be an integral or enum value static constexpr auto value = "prvalue"; }; template<typename T> struct value_category<T&> { static constexpr auto value = "lvalue"; }; template<typename T> struct value_category<T&&> { static constexpr auto value = "xvalue"; }; // Double parens for ensuring we inspect an expression, // not an entity #define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value int main() { std::cout << VALUE_CATEGORY( static_cast<std::remove_reference<const char[1]>::type&&>("") ) << std::endl ; }
generates the following answer from gcc and clang using Wandbox:
xvalue
and this answer from Visual Studio using webcompiler:
lvalue
hence the error from Visual Studio for the original code:
You cannot bind an lvalue to an rvalue reference
when it attempt to bind the result of static_cast<typename std::remove_reference<T>::type&&>(t)
to std::remove_reference<T>::type&&
which is the return value of std::move
.
I don't see any reason why the static_cast
should generate an lvalue as it does in the Visual Studio case.
Let's start by breaking this up into two pieces, so we can analyze each separately:
#include <string> void foo() { auto x = std::move(""); std::string s(x); }
The second part, that initializes the string from x
(whatever type that might happen to be) isn't really the problem or question at hand here. The question at hand (at least as it seems to me) is the first line, where we try to bind an rvalue reference to a string literal.
The relevant part of the standard for that would be [dcl.init.ref]/5
(§8.5.3/5, at least in most versions of the C++ standard I've seen).
This starts with:
A reference to type ''cv1 T1'' is initialized by an expression of type ''cv2 T2'' as follows.
That's followed by a bullet list. The first item covers only lvalue references, so we'll ignore it. The second item says:
if the initializer expression
- is an xvalue (but not a bit-field) class prvalue, array prvalue or function lvalue[...]
- has class type (i.e.,T2
is a class type)[...]
- Otherwise - ifT1
orT2
is a class type and T1 is not is not reference related to T2 [...]
Clearly none of those applies. A string literal is not an xvalue, class prvalue, array prvalue or function lvalue, nor does it have class type.
That leaves only:
If
T1
is reference-related toT2
:
- cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and
- if the reference is an rvalue reference, the initializer expression shall not be an lvalue.
Since, in this case, the type of the result of the conversion is being deduced by the compiler, it will be reference-related to type of the initializer. In this case, as the part I've emphasized says, the initializer expression can't be an lvalue.
That leaves only the question of whether a string literal is an lvalue. At least offhand, I can't immediately find the section of the C++ standard that says they are (there's no mention of it in the section on string literals). If it's absent, the next step would be to look at the base document (the C standard) which clearly states that string literals are lvalues (N1570, §6.5.1/4)
A string literal is a primary expression. It is an lvalue with type as detailed in 6.4.5.
I do wish I could find a direct statement to that effect in the C++ standard (I'm pretty sure it should exist), but for whatever reason I'm not finding it right now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With