std::move строкового литерала - какой компилятор правильный?

Учитывая следующий код:

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

Это компилируется с Apple Clang (XCode 7) и не с Visual Studio 2015, которая генерирует следующую ошибку:

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

Не обращая внимания на момент, когда перемещение является избыточным, какая реализация стандартной библиотеки является более правильной в этом случае?

Я чувствую, что тип "" является const char[1] так std::move должен вернуться std::remove_reference<const char[1]&>::type&& который был бы const char[1]&&,

Мне кажется, что это должно распасться на const char*,

Или я неправильно понимаю правила?

2 ответа

Решение

Это похоже на ошибку Visual Studio. Это сводится к std::move, и если мы посмотрим на страницу cppreference, она имеет следующую подпись:

template< class T >
typename std::remove_reference<T>::type&& move( T&& t );

и он возвращает:

static_cast<typename std::remove_reference<T>::type&&>(t) 

который соответствует черновому стандартному разделу C++ 20.2.4 вперед / двигаться помощники [вперед].

Используя код, который я взял отсюда, мы можем увидеть следующий пример:

#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 ;

}

генерирует следующий ответ от gcc и clang, используя Wandbox:

xvalue

и этот ответ из Visual Studio с помощью веб-компилятора:

lvalue

отсюда ошибка из Visual Studio для оригинального кода:

Вы не можете привязать lvalue к ссылке на rvalue

когда он пытается связать результат static_cast<typename std::remove_reference<T>::type&&>(t) в std::remove_reference<T>::type&& который является возвращаемым значением std::move,

Я не вижу причин, почему static_cast должен генерировать lvalue, как это происходит в случае Visual Studio.

Давайте начнем с разбивки этого на две части, чтобы мы могли проанализировать каждый в отдельности:

#include <string>
void foo()
{
    auto x = std::move("");
    std::string s(x);
}

Вторая часть, которая инициализирует строку из x (какого бы типа это ни было), на самом деле это не проблема и не вопрос. Вопрос под рукой (по крайней мере, как мне кажется) - это первая строка, где мы пытаемся связать ссылку на rvalue со строковым литералом.

Соответствующая часть стандарта для этого будет [dcl.init.ref]/5 (§8.5.3/5, по крайней мере в большинстве версий стандарта C++, которые я видел).

Это начинается с:

Ссылка на тип ''cv1 T1 '' инициализируется выражением типа ''cv2 T2 '' следующим образом.

Затем следует список с маркерами. Первый элемент охватывает только ссылки lvalue, поэтому мы его проигнорируем. Второй пункт говорит:

если выражение инициализатора
- это xvalue (но не битовое поле) класс prvalue, массив prvalue или функция lvalue [...]
- имеет тип класса (т.е. T2 это тип класса) [...]
- в противном случае - если T1 или же T2 является типом класса, и T1 не является ссылкой, связанной с T2 [...]

Очевидно, что ни один из них не применим. Строковый литерал не является xvalue, классом prvalue, массивом prvalue или функцией lvalue, и при этом он не имеет типа класса.

Это оставляет только:

Если T1 имеет отношение к T2:
- cv1 должен иметь ту же квалификацию cv, что и квалификация cv, или более высокую, чем cv2, и
- если ссылка является ссылкой rvalue, выражение инициализатора не должно быть lvalue.

Поскольку в этом случае тип результата преобразования определяется компилятором, он будет связан со ссылкой на тип инициализатора. В этом случае, как подчеркивается в части, выражение инициализатора не может быть lvalue.

Остается только вопрос о том, является ли строковый литерал l-значением. По крайней мере, случайно, я не могу сразу найти раздел стандарта C++, в котором говорится, что они есть (об этом нет упоминания в разделе о строковых литералах). Если он отсутствует, следующим шагом будет просмотр базового документа (стандарт C), в котором четко указано, что строковые литералы являются lvalues ​​(N1570, §6.5.1/4).

Строковый литерал является основным выражением. Это lvalue с типом, как описано в 6.4.5.

Хотелось бы найти прямое заявление на этот счет в стандарте C++ (я уверен, что оно должно существовать), но по какой-то причине я не могу найти его прямо сейчас.

Другие вопросы по тегам