简体   繁体   中英

std::move of string literal - which compiler is correct?

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?

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 (ie, T2 is a class type)[...]
- Otherwise - if T1 or T2 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 to T2 :
- 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM