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++ (я уверен, что оно должно существовать), но по какой-то причине я не могу найти его прямо сейчас.