Подтвердить что ты не робот

Std:: move string literal - какой компилятор прав?

С учетом следующего кода:

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

Это компиляция с яблочным clang (xcode 7) и не с визуальной студией 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*.

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

4b9b3361

Ответ 1

Это выглядит как ошибка 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) 

который соответствует стандартным разделам проекта С++ 20.2.4 forward/move helers [forward].

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

#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 с помощью webcompiler:

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.

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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