27

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?

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 11
    Compiles on [g++ 5.2.0](http://coliru.stacked-crooked.com/a/5547385a86e74d19) also. My bet is Visual Studio is wrong, since it usually is ... – Claudiu Dec 08 '15 at 16:13
  • 4
    I think you summed it all up nicely in your question. MSVC is wrong, not the first time around. – SergeyA Dec 08 '15 at 16:13
  • 3
    What error does MSVC produce, specifically? – edmz Dec 08 '15 at 16:24
  • 1
    if the deduced type in std::move is const, then the move should fail. Can somebody explain why MSVC is wrong? – Shyamal Desai Dec 08 '15 at 16:25
  • 2
    @Shyamal Desai Does the move fail, or should the move silently fall back to copy? – Mark B Dec 08 '15 at 16:28
  • @black I must confess that I can't remember, and visual studio freezes on startup for me as of today. Perhaps someone can check for me? – Richard Hodges Dec 08 '15 at 16:29
  • 1
    The error is : You cannot bind an lvalue to an rvalue reference : see reference to function template instantiation 'const char (&&std::move(_Ty) throw())[1]' being compiled with [ _Ty=const char (&)[1] ] – Shyamal Desai Dec 08 '15 at 16:30
  • 2
    @ShyamalDesai since string has a constructor that takes const char *, I _think_ that const char[]&& should decay automatically, allowing an overload match. – Richard Hodges Dec 08 '15 at 16:32
  • @Mark: If there is a constructor that takes a const value, then it will decay to a copy. Thanks Richard. That makes sense. – Shyamal Desai Dec 08 '15 at 16:35
  • 5
    @RichardHodges: It should, and it probably would *if things got that far*. The problem (and real question) is whether you can bind an rvalue reference to a string literal in the first place though. I'm looking through the standard now, but so far haven't found any clear statement in either direction. – Jerry Coffin Dec 08 '15 at 16:36
  • 1
    I don't see any reason why `std::move` on a string literal would be invalid; it's just not useful. rvalue references to arrays are legal and you can have array prvalues. – Simple Dec 08 '15 at 16:39
  • @RichardHodges Your thought is right. There is an array-to-pointer conversion for the candidate constructor, which takes `char const *` as the first argument: 13.3.3.1\1, 3.1; 4.2\1 [quote]An lvalue or rvalue of type “array of N T” [/quote] – Eugene Zavidovsky Dec 08 '15 at 19:01
  • @black updated with the error, as noted below you can also test using webcompiler – Shafik Yaghmour Dec 09 '15 at 01:26
  • 1
    Do you get the error from just writing `std::move("");` , or does it only occur when using the result as you do? – M.M Dec 09 '15 at 03:18
  • @M.M ` std::move("");` is sufficient, I would have suggested from the rest but the first answer would not make as much sense if it was edited. It does not change the answer. – Shafik Yaghmour Dec 09 '15 at 03:43

2 Answers2

11

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.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
4

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 4
    Your split is not valid, since auto deduction doesn't preserve the exact type. – Sebastian Redl Dec 08 '15 at 17:23
  • But wait, the argument to std::move is a universal reference since its evaluated in deduced context. This makes the l-value string literal legal, right? – Richard Hodges Dec 08 '15 at 18:02
  • 2
    Found that c++ reference for you: 5.1.1/1: "A literal is a primary expression. Its type depends on its form (2.14). A string literal is an lvalue; all other literals are prvalues." – Richard Hodges Dec 08 '15 at 18:31