Как шаблон шаблона может хранить ссылку или значение?

Чтение о универсальных ссылках заставило меня задаться вопросом: как я могу построить шаблон класса таким образом, чтобы он сохранялся по ссылке, если это возможно, или по значению если это необходимо?

То есть, могу ли я сделать что-то вроде этого

template <class T>
class holder {
    T  obj_m;  // should be a reference if possible...
public:
    holder(T t) :obj_m { t } {}
}

auto 
hold_this(T && t) { return holder<T>(t); }

За исключением того, что если hold_this() задано lvalue, держатель будет удерживать ссылку, а если задано r-значение, то держатель сделает копию?

Ответ 1

За исключением того, что, когда hold_this() задано lvalue, держатель будет удерживать ссылку, а когда задано значение r, владелец сделает копию?

Вы уже написали (минус требуемый template <typename T>). Правила вычета для ссылки на пересылку сохраняют следующую категорию:

  • Если t привязано к lvalue типа T2, то T = T2&.
  • Если t связано с r значением типа T2, то T = T2.

Это те правила вычета, которые std::forward полагается на выполнение своей работы. И зачем нам также передавать этот тип.

Вышеупомянутое означает, что вы создаете экземпляр holder непосредственно с помощью T2 в случае rvalue. Давать вам именно то, что вы хотите. Выполняется копия.

На самом деле делаются две копии. Один раз, чтобы создать аргумент конструктора t, а другая копия - инициализировать из него obj_m. Но мы можем избавиться от него с помощью умного использования type_traits:

template <class T>
class holder {
    T  obj_m;  // should be a reference if possible...
public:
    holder(std::add_rvalue_reference_t<T> t) :obj_m { std::forward<T>(t) } {}
};

template<typename T>
auto hold_this(T && t) { return holder<T>(std::forward<T>(t)); }

Посмотрите в прямом эфире. Мы используем add_rvalue_reference_t, чтобы сделать t правильным типом ссылки в каждом случае. И "имитируйте" вывод аргумента, который сделает obj_m { std::forward<T>(t) } разрешено инициализировать obj_m из правильного ссылочного типа.

Я говорю "имитировать", потому что важно понять аргумент конструктора для holder, не может быть ссылкой пересылки, потому что сам конструктор не является шаблоном.


Кстати, поскольку вы отметили , мы также можем добавить руководство для выписки на ваш пример. Если мы определим его следующим образом (с обратной связью от T.C.):

template <class T>
class holder {
    T  obj_m;  // should be a reference if possible...
public:
    holder(T&& t) :obj_m { std::forward<T>(t) } {}
};

template<typename T>
holder(T&&) -> holder<T>;

Затем этот живой пример показывает, что вы можете определить переменные как hold h1{t}; и hold h2{test()};, с теми же выводимыми типами, что и функция return значения из ранее.