Универсальная ссылка с шаблонным классом

Пример:

template <typename T>
class Bar
{
public:
    void foo(T&& arg)
    {
        std::forward<T>(arg);
    }
};

Bar<int> bar;    

bar.foo(10); // works

int a{ 10 };
bar.foo(a); // error C2664: cannot convert argument 1 from 'int' to 'int &&'

Кажется, что универсальные ссылки работают только с шаблонными функциями и только с выводом типа, верно? Так что нет смысла использовать его с классом? И имеет ли смысл использование std::forward в моем случае?


person nikitablack    schedule 01.06.2015    source источник


Ответы (2)


Обратите внимание, что предпочтительная терминология (то есть та, которая будет использоваться в будущих версиях спецификации) теперь называется ссылка на пересылку.

Как вы говорите, ссылка на пересылку работает только с выводом типа в шаблоне функции. В вашем случае, когда вы говорите T&&, T это int. Это не может быть int&, потому что это было явно указано в вашем экземпляре Bar. Таким образом, правила свертывания ссылок не могут быть реализованы, поэтому вы не можете выполнить идеальную пересылку.

Если вы хотите сделать идеальную пересылку в такой функции-члене, вам нужен шаблон функции-члена:

template <typename U>
void foo(U&& arg)
{
    std::forward<U>(arg); //actually do something here
}

Если вам абсолютно необходимо, чтобы U имел тот же неквалифицированный тип, что и T, вы можете сделать static_assert:

template <typename U>
void foo(U&& arg)
{
    static_assert(std::is_same<std::decay_t<U>,std::decay_t<T>>::value, 
                  "U must be the same as T");
    std::forward<U>(arg); //actually do something here
}

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

template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename T, typename U>
using is_equiv = std::is_same<remove_cv_ref<T>, remove_cv_ref<U>>;

Если вам нужна вариационная версия, мы можем написать трейт are_equiv. Сначала нам нужен трейт, чтобы проверить, все ли трейты в пакете истинны. Я буду использовать метод bool_pack:

namespace detail
{
    template<bool...> struct bool_pack;
    template<bool... bs>
    using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
}
template <typename... Ts>
using all_true = detail::all_true<Ts::value...>;

Затем нам нужно что-то, чтобы проверить, удовлетворяет ли каждая пара типов в Ts... и Us... is_equiv. Мы не можем использовать два пакета параметров в качестве аргументов шаблона, поэтому я буду использовать std::tuple для их разделения (вы можете использовать дозорный узел или вместо этого разделить пакет на полпути, если хотите):

template <typename TTuple, typename UTuple>
struct are_equiv;

template <typename... Ts, typename... Us>
struct are_equiv <std::tuple<Ts...>, std::tuple<Us...>> : all_true<is_equiv<Ts,Us>...>
{};

Тогда мы можем использовать это как:

static_assert(are_equiv<std::tuple<Ts...>,std::tuple<Us...>>::value, 
              "Us must be equivalent to Ts");
person TartanLlama    schedule 01.06.2015
comment
Спасибо. static_assert мне подходит. Есть ли что-то подобное, что я могу использовать с вариативными аргументами шаблона в стандартной библиотеке? - person nikitablack; 01.06.2015
comment
Я не уверен, что ты имеешь в виду. Что-то похожее на static_assert, которое принимает несколько аргументов? - person TartanLlama; 01.06.2015
comment
В яблочко. У меня есть класс с template <typename... T> и функцией, которую я хочу объявить как foo(U&&... args). Так что мне нужно сравнить и T и U. Я могу написать хелпер, но, возможно, это уже сделано в stl. Я просто не могу найти его. - person nikitablack; 01.06.2015
comment
Обновил мой ответ возможным решением. - person TartanLlama; 01.06.2015
comment
Вау, это выглядит действительно страшно. К сожалению, я не могу понять эти магические символы (кстати, не могли бы вы посоветовать хороший справочник, где я могу изучить эти продвинутые вещи?). Все, что я могу сказать - он не компилируется с VS2013. Выдает ошибку 'std::is_same<remove_cv<_Ty>::type,remove_cv<remove_reference<Us>::type>::type>...' - person nikitablack; 01.06.2015
comment
Вероятно, я использую некоторые языковые функции, не поддерживаемые в VS2013, или сталкиваюсь с ошибкой компилятора. Что касается ссылок, лучший способ понять это — просто попробовать, посмотреть другие сообщения с тегами c++ и templates и т. д. - person TartanLlama; 01.06.2015

Вы правы: «универсальные ссылки» появляются только в том случае, если тип параметра deduced равен T&&. В вашем случае нет вывода (T известно из класса), следовательно, нет универсальной ссылки.

В вашем фрагменте std::forward всегда будет выполнять std::move, поскольку arg является обычной ссылкой на rvalue.

Если вы хотите сгенерировать универсальную ссылку, вам нужно сделать foo шаблоном функции:

template <typename T>
class Bar
{
public:
    template <typename U>
    void foo(U&& arg)
    {
        std::forward<U>(arg);
    }
};
person Quentin    schedule 01.06.2015
comment
Это T есть int& ваш вывод не соответствует действительности, pr по крайней мере неоднозначен. Можете ли вы уточнить, что вы пытаетесь сказать, если вы говорите, что они только появляются, и продемонстрировать, что ссылки пересылки не являются супер-магией? - person Yakk - Adam Nevraumont; 01.06.2015
comment
@Yakk Насколько мне известно, универсальная (или пересылающая) ссылка — это имя, данное пересечению вывода типа, ссылок rvalue и свертывания ссылок. Я, правда, не рассматривал случай, когда T сам по себе является ссылкой. Является ли это по-прежнему пересылаемой ссылкой, если пересылаемый объект всегда относится к ссылочному типу? - person Quentin; 01.06.2015
comment
Якк считает, что если T равно int&, arg также будет иметь тип int&, то есть будут применяться правила свертывания ссылок, но это не ссылка для пересылки, поскольку она не находится в дедуцируемом контексте. - person TartanLlama; 01.06.2015
comment
И относительно стандартное использование - вывести тип в тип. Используйте вспомогательную функцию для получения T в дедукции, создайте экземпляр foo_t<T>. Разделяет вычет от использования, но... - person Yakk - Adam Nevraumont; 01.06.2015