вспомогательная конструкция make_XYZ, разрешающая RVO и вывод типа, даже если XZY не имеет ограничения на копирование

ОБНОВЛЕНИЕ 1. В C ++ 17 добавлен вывод типов для конструкторов, что не означает, что бесплатная функция является второстепенным решением.

ОБНОВЛЕНИЕ 2: C ++ 17 добавил гарантированное исключение копии (копирование даже не происходит концептуально). Итак, с C ++ 17 мой код действительно работает и с оптимальной производительностью. Но код Мартиньо, использующий инициализацию скобок для возвращаемого значения, по-прежнему является более чистым решением, я считаю. Но ознакомьтесь с этим ответом Барри и комментарием T.C.

СТАРЫЙ ЗАПИСЬ: определение типа не работает для конструкторов (по крайней мере, до C ++ 11 включительно). Распространенное решение - полагаться на RVO (оптимизация возвращаемого значения) и писать шаблонную функцию make_XYZ, которая пересылает свои параметры конструктору. Пример - std::make_tuple.

Любой шаблонный акробат, который знает обходной путь, чтобы заставить эту работу работать, когда политика запрета на копирование мешает? Действительное решение должно по-прежнему позволять RVO происходить.

Кроме того, исчезнет ли требование make_XYZ в C ++ 14?

#include <iostream>

template <typename T>
struct XYZ
{
    // remove following two lines to make the code compile
    XYZ (XYZ const & rhs) = delete; 
    XYZ (XYZ && rhs) = delete; 
    T i;
    XYZ (T i):i(i)
    {
    }
};

template <typename T>
XYZ<T> make_XYZ (T && i)
{
    return XYZ<T>(std::forward<T>(i));
}

int main ()
{
    auto x = make_XYZ(1);
    std::cout << x.i << std::endl;
}

person Patrick Fromberg    schedule 17.10.2013    source источник
comment
Что, если вы измените тип возвращаемого значения make_XYZ на const-reference? Я не уверен, нужно ли вам также перейти на auto const& на сайте вызова.   -  person John Zwinck    schedule 17.10.2013
comment
@JohnZwinck, нет, это вылетит   -  person Patrick Fromberg    schedule 17.10.2013
comment
Уверены ли вы? Существуют специальные правила для пожизненного продвижения референта константных ссылок. Подробнее об этом можно прочитать здесь: stackoverflow.com/questions/2615162/   -  person John Zwinck    schedule 17.10.2013
comment
@JohnZwinck: Они не позволяют волшебным образом поддерживать локальные переменные функции. Я повторяю: ref-to-const не позволяет использовать refs для возвращенных локальных жителей.   -  person Lightness Races in Orbit    schedule 17.10.2013
comment
Да, ты прав, здесь это не поможет. Вздох, C ++. :)   -  person John Zwinck    schedule 17.10.2013
comment
Если ctor не является явным, вы можете использовать список инициализации в фигурных скобках непосредственно в операторе возврата. Йоханнес Шауб имеет предложил этот подход здесь.   -  person dyp    schedule 17.10.2013
comment
@JohnZwinck, извините, я забираю свой комментарий по поводу аварии. Даже ответы DyPs и Martinhos правильные, вы на самом деле хорошо здесь замечаете.   -  person Patrick Fromberg    schedule 17.10.2013
comment
@Patrick, вы не можете вернуть ссылку на локальную переменную или временную. Он умирает, когда функция завершается. Это может или не может дать сбой, поскольку такова природа неопределенного поведения.   -  person R. Martinho Fernandes    schedule 17.10.2013
comment
@ R.MartinhoFernandes, решение auto const & явно неоптимально, а constref у меня даже не работает. Однако почему здесь нельзя применять этот ответ? Кажется, я все еще чего-то упускаю (что-то, что мне было ясно, когда я знал меньше).   -  person Patrick Fromberg    schedule 17.10.2013
comment
Продление срока службы применяется только в том случае, если вы привязываете временный напрямую к ссылке. Он не является транзитивным, т.е. если вы привяжете новую ссылку к той же ссылке, вы не получите другого продления срока службы. Если вы вернетесь по ссылке, привязка другой ссылки к этой больше не сделает функцию локальной. Для T f(); T const& r = f(); это работает, потому что f() создает временное, привязано к r и продлевает время жизни. Но для T const& f(); T const& r = f(); это не так, потому что f() дает ссылку, а не временную ссылку.   -  person R. Martinho Fernandes    schedule 17.10.2013


Ответы (2)


Если есть неявный конструктор, действительно можно вернуть не копируемый и неподвижный тип по значению. См. Живой пример: http://coliru.stacked-crooked.com/a/89ef9d3115924558.

template <typename T>
XYZ<T> make_XYZ (T && i)
{
    return { std::forward<T>(i) };
}

Сложность здесь в том, что { ... } не создает временное значение и не перемещает его к возвращаемому значению. Он напрямую инициализирует возвращаемое значение. Нет ни копирования, ни перемещения, и это не имеет значения, применяется ли какая-либо оптимизация (она не будет компилироваться, если для ее работы требуется оптимизация).

Однако, поскольку тип нельзя копировать или перемещать, вы не сможете сохранить его в локальной переменной по значению. Однако вы можете использовать старый трюк с временным продлением срока службы, чтобы удержать его:

auto&& x = make_XYZ(1);
person R. Martinho Fernandes    schedule 17.10.2013
comment
+1 фантастика! Я думал, что XYZ<>, по крайней мере, должен будет разрешить конструктор перемещения или назначение перемещения, но это делает это без них (ни конструктора копирования, ни присваивания копирования). - person Walter; 17.10.2013
comment
Я знаю, что это противоречит правилам комментирования. Но я не могу этого скрыть. Я впечатлен! - person Patrick Fromberg; 17.10.2013
comment
Из вопроса: допустимое решение должно по-прежнему позволять RVO происходить! - этот ответ объясняет, почему следует игнорировать произвольные ограничения ответов в вопросе и почему SO требует практических задач. :) - person Yakk - Adam Nevraumont; 17.10.2013
comment
Это действительно здорово! К сожалению, есть случай (вероятно, редкий), когда это не работает: когда XYZ<T> имеет конструктор, который принимает std::initializer_list<T> (в этом случае этот конструктор перехватывает вызов XYZ(T)). Мое обходное решение следующее: создайте struct tag {};' then add this constructor XYZ (tag, T i): XYZ (i) {} `(важно: это (i), а не {i}) и в make_XYZ сделайте return { tag{}, std::forward<T>(i) };. Может быть, стоит сделать tag и делегирующий конструктор private членами XYZ<T> и make_XYZ<T> a friend. Что вы думаете? (Обригадо). - person Cassio Neri; 14.11.2013
comment
@CassioNeri: да, это звучит как надежный план. - person R. Martinho Fernandes; 14.11.2013
comment
Я просто хочу поделиться этой связанной ссылкой, касающейся инициализации копии - person Patrick Fromberg; 13.12.2013

RVO - это всего лишь оптимизация; копирование / перемещение должно быть доступно для использования при возврате объекта (временного или именованного) из функции.

Я бы предложил использовать make_XYZ только в неоцененном контексте, используя decltype:

#include <utility>

struct noncopy {
    noncopy() {}
    noncopy(noncopy &&) = delete;
    noncopy(const noncopy &) = delete;
};

template<class T1, class T2>
struct noncopy_pair: public std::pair<T1, T2>, private noncopy {
    using std::pair<T1, T2>::pair;
};

template<class T1, class T2>
noncopy_pair<T1, T2> make_noncopy_pair(T1 &&t, T2 &&u);

int main() {
    auto &&x = decltype(make_noncopy_pair(1, 'c'))(1, 'c');
}

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

person ecatmur    schedule 17.10.2013
comment
К сожалению, Мартиньо доказал, что вы ошибались: копирование / перемещение не обязательно должно быть доступно !. неудача. - person Walter; 17.10.2013
comment
Копирование / перемещение @Walter должно быть доступно для RVO; Решение Мартиньо не использует RVO. - person ecatmur; 17.10.2013
comment
Да, но ваш ответ может быть истолкован как означающий, что для решения проблемы OP должно быть доступно копирование / перемещение. И вот как я это читаю (и до сих пор читаю). - person Walter; 17.10.2013
comment
@Walter Я попытался уточнить свой ответ; Ключ к новому синтаксису заключается в том, что он не предполагает возврата из функции фактического объекта. - person ecatmur; 17.10.2013