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

后端 未结 2 831
一个人的身影
一个人的身影 2021-01-07 17:01

Given the following code:

#include 
void foo()
{
  std::string s(std::move(\"\"));
}

This compiles with apple clang (xcode 7)

相关标签:
2条回答
  • 2021-01-07 17:50

    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 - 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.

    0 讨论(0)
  • 2021-01-07 17:54

    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.

    0 讨论(0)
提交回复
热议问题