Множественные возвращаемые значения (структурированные привязки) с неизменяемыми типами и гарантированное RVO в С++ 17

С С++ 17 у нас будет возможность возвращать невозмутимые (в том числе непокрываемые) типы, такие как std::mutex, через то, что можно считать гарантированной оптимизацией возвращаемого значения (RVO): Гарантированное копирование с помощью упрощенных категорий значений:

struct nocopy { nocopy(nocopy&) = delete; nocopy() = default; };
auto getRVO(){
    return nocopy();
}

У нас также будет структурированные привязки, позволяющие:

tuple<T1,T2,T3> f();
auto [x,y,z] = f();

или (здесь также используется мое понимание функции вывод аргумента шаблона для конструкторов)

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};
// (Original questions missed 'many' on the next line. Thanks, T.C.)
auto f(){ return many{string(),5.7, false} }; 
auto [x,y,z] = f();

Но позволяют ли эти функции включить что-то подобное?

auto get_ensured_rvo_str(){
    return std::pair(std::string(),nocopy());
}

auto get_class_and_mutex(){
    return many{SomeClass(),std::mutex(),std::string()};
}

int main(){
    auto rvoStr = get_ensured_rvo_str().first;
    auto [ mtx,sc,str ] = get_class_and_mutex();
}

Мое мышление заключается в том, что для этого для работы потребовалось бы гарантированное RVO аргументов конструктора агрегата при формировании std::tuple или many, но не было бы это названо RVO (NRVO), которое специально не включено в Предложение P0144R2?


Боковое примечание: P0144R2 специально упоминает, что поддерживаются типы только для перемещения:

2.6 Типы только для перемещения

Поддерживаются типы только для перемещения. Например:

struct S { int i; unique_ptr<widget> w; };
S f() { return {0, make_unique<widget>()}; }
auto [ my_i, my_w ] = f();

Ответ 1

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};
auto f(){ return {string(),5.7, false} };

Это не будет компилироваться. Сначала вы никогда не говорили, что f должен вернуть many. Во-вторых, декомпозиция аргументов шаблона шаблона работает с конструкторами, а единственными конструкторами many являются неявно объявленные конструкторы по умолчанию, копирование и перемещение.

Вам нужен проводник:

template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;
auto get_ensured_rvo_str(){
    return std::pair(std::string(),nocopy());
}

Это тоже не работает. nocopy() материализуется во временное, которое привязано к эталонному параметру конструктора pair, который затем пытается перейти от него и не работает. Никакое исключение этого временного не возможно или разрешено.

(Конечно, как указывает Никол Болас в своем ответе, доступ к члену класса в get_ensured_rvo_str().first материализует возвращаемое значение pair get_ensured_rvo_str, поэтому rvoStr на самом деле будет перемещен, построенный из first член этого материализованного временного, но здесь у вас есть проблема задолго до этого.)

auto get_class_and_mutex(){
    return many{SomeClass(),std::mutex(),std::string()};
}
auto [ mtx,sc,str ] = get_class_and_mutex();

Это хорошо (если у вас есть руководство по вычету). Агрегатная инициализация не вызывает конструктор many; он инициализирует элементы напрямую соответствующим инициализатором prvalue.

Ответ 2

Структурированное связывание определено для работы на основе извлечения ссылок или псевдо-ссылок на отдельные значения. То есть, если вы это сделаете:

auto [x,y,z] = f();

Что вы получите, это примерно так:

auto HIDDEN_VALUE = f();
auto &x = get<0>(HIDDEN_VALUE);
auto &y = get<1>(HIDDEN_VALUE);
auto &z = get<2>(HIDDEN_VALUE);

При работе с structs x, y и z не будут ссылки; они будут тем, что "ссылается" на фактического члена массива, но это не фактическая ссылка. Главное, что x, y и z никогда не являются копиями чего-либо.

Таким образом, возникает вопрос, скопировано ли HIDDEN_VALUE. И ясно, что HIDDEN_VALUE построено значение. И, таким образом, если возврат f() является prvalue, тогда применяются правила гарантированного elision.

auto rvoStr = get_ensured_rvo_str().first;

Выражение get_ensured_rvo_str() является prvalue. Однако результат применения .first к нему не является prvalue. Применение .first заставляет prvalue (при гарантированных правилах прав) создавать временный, при этом к нему применяется .first. Элемент, извлеченный, который является значением x, будет использоваться для копирования инициализации rvoStr.

Таким образом, ни одна версия стандарта не копируется в rvoStr elided.

return many{SomeClass(),std::mutex(),std::string()};
...
auto [ mtx,sc,str ] = get_class_and_mutex();

Я собираюсь предположить, что вы внесли необходимые дополнения, необходимые для компиляции оператора return.

Учитывая, что конструкция в функции будет непосредственно инициализировать HIDDEN_VALUE на сайте возврата. И каждый из членов агрегата будет инициализирован непосредственно prvalues, поэтому копирование не произойдет.