Строка string ( "Обычная старая цепочка C" ) rvalue?

Мне было интересно, будет ли копирование в вызове foo(string) ниже. (Примечание: foo(string) принадлежит интерфейсу, который я не могу изменить).

Для этого я попытался проверить, построил ли string("Hello world!") rvalue.

Я искал на SO, как это сделать программно, и нашел это сообщение: Как определить программно, если выражение rvalue или lvalue в С++?

void foo( string str)
{
    cout << str << endl;
}

int main()
{
    foo("Hello world!");
    cout << is_rvalue_reference<decltype(string("Hello world!"))>::value  << endl;
}

результат

Hello world!
0

Я думал, что получаю true до is_rvalue_reference< xxx >::value

  • Где я ошибаюсь?
  • string("Hello world!") может быть rvalue, но, похоже, не является "ссылкой любого вида" (либо lvalue, rvalue, universal...), так что я получил результат false. Есть ли способ получить ответ true в случае rvalue?
  • Есть ли в его примере копия или нет?

Ответ 1

  • Где я ошибаюсь?

std::string("") - это rvalue, но decltype(std::string("")) не является ссылкой на rvalue. Тип объекта std::string - это... std::string, конечно.

У вас есть ошибка категории. Rvalue является своего рода выражением, ссылка rvalue является своего рода типом.

Временным объектом string является rvalue. Тип string&& является ссылочным типом rvalue.

Ваше выражение decltype не полезно для того, что вы пытаетесь сделать. Рассмотрим:

std::string s;
using type1 = decltype(s);
using type2 = decltype(std::string(""));
static_assert(std::is_same<type1, type2>::value, "same");

В обоих случаях decltype дает один и тот же тип: std::string. Это потому, что decltype рассказывает вам о типах, а не о категориях значений (то есть, является ли выражение значением rvalue или lvalue).

Если вы хотите узнать, является ли выражение значением rvalue или значением l, вы должны знать больше, чем только его тип. В случае decltype(std::string("")) вы создаете неназванное временное значение, которое является значением rvalue. Вам не нужно просить свой тип об этом знать.

  • string ( "Hello world!" ) может быть rvalue, но, похоже, не является "ссылкой любого вида" (lvalue, rvalue, universal...), так что я получил false результат. Есть ли способ получить истинный ответ в случае rvalue?

Что вы подразумеваете под "истинным" ответом? Вы просто имеете в виду черту типа, которая даст результат true?

Вы можете спросить, может ли тип конвертироваться в ссылку rvalue:

std::is_convertible<decltype(std::string("")), std::string&&>::value

Это скажет вам, можете ли вы привязать ссылку rvalue к объекту. Но это глупый вопрос: конечно, вы можете связать ссылку rvalue типа X && к временному типу X. Вам никогда не придется спрашивать об этом.

И вообще, вы не выполняете аргумент типа string&&, поэтому задаете этот вопрос, даже не сообщая вам о своем вызове foo(std::string).

  • Есть ли в его примере копия или нет?

Да, инициализация аргумента функции из временной строки не должна делать никаких копий или ходов, их следует исключить. В стандарте С++ 14 в [class.copy] p31 говорится, что копирование/перемещение может быть отменено, если временный объект (который не привязан к ссылке) будет скопирован/перенесен в объект класса того же типа. Это условие выполняется при инициализации аргумента функции типа класса из временного типа того же типа. Компилятор, который не выполняет это исключение (по крайней мере, когда оптимизация включена или в сборке "release" ) является плохим компилятором.

Существует объяснение правил копирования текста в http://en.cppreference.com/w/cpp/language/copy_elision - см. часть о безымянной временной.

Ответ 2

Где я ошибаюсь?

Ссылка rvalue имеет следующий вид: T&&.

decltype(string("")) оценивается как string, а не string&& - поэтому он не является ссылкой rvalue.

is_same<decltype(string("Hello world!")), string>::value // true

string ( "Hello world!" ) может быть rvalue, но, похоже, не является "ссылкой любого типа"

string("Hello world!") - это выражение с категорией значений rvalue, точнее это значение prvalue. Выражения не ссылки. (Дополнительная информация: "В С++ какие выражения приводят к ссылочному типу при применении к ним метода decltype?" .)


Есть ли способ получить истинный ответ в случае rvalue?

Вы можете использовать преобразование, которое:

  • Учитывая значение lvalue, возвращает ссылку lvalue.

  • Учитывая значение rvalue, возвращает ссылку rvalue.

Поведение, упомянутое выше, может быть получено благодаря правила вывода аргументов шаблона:

template <typename T>
using is_rvalue = std::is_rvalue_reference<T&&>;

is_rvalue<decltype(string(""))>::value // true
is_rvalue<decltype(std::declval<string&&>())>::value // true
is_rvalue<decltype(std::declval<string&>())>::value // false

Код выше работает, потому что:

  • T&& выводится как T&& как для значений x, так и для prvalues.

  • T&& выводится как T& в противном случае (т.е. для lvalues).


Есть ли в его примере копия или нет?

В С++ 14 компилятору разрешено (но не обязательно), чтобы исключить копии в вашем примере.

(Кредиты Джонатану Вакели для определения соответствующих стандартных кавычек С++ 14, приведенных ниже.)

Соответствующая стандартная черновик С++ 14 (N4296):

  • §12.8 [class.copy] стр .31

    При выполнении определенных критериев реализация допускает опустить конструкцию копирования/перемещения класса объект, даже если конструктор, выбранный для операции копирования/перемещения и/или деструктор для объекта имеют побочные эффекты. В таких случаях реализация рассматривает источник и цель опущенной копии/перемещения как просто два разных способа обращения к одному и тому же объекту и уничтожение этого объекта происходит в более поздние времена, когда два объекта были бы уничтожены без оптимизации. Это исключение операций копирования/перемещения, называемое копированием, разрешено в следующих случаях ( могут быть объединены для устранения нескольких копий):

    [...]

    [p.31.3], когда объект временного класса, который не был привязан к ссылке, будет скопирован/перемещен   к объекту класса с тем же cv-неквалифицированным типом операция копирования/перемещения может быть опущена   построение временного объекта непосредственно в цель пропущенного копирования/перемещения

  • §5.2.2 [expr.call] p.4

    Когда вызывается функция, каждый параметр должен быть инициализирован соответствующим аргумент. [...] В течение инициализация параметра, реализация может избежать создания дополнительных временных объединение преобразований по соответствующему аргументу и/или построение временных инициализация параметра. [...]

ИСО/МЭК. (2014). ISO Международный стандарт ISO/IEC 14882: 2014 (E) - Язык программирования С++. [Рабочий проект]. Женева, Швейцария: Международная организация по стандартизации (ИСО). (Получено с https://isocpp.org/std/the-standard)