Универсална справка с шаблонен клас

Пример:

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

Прав сте: „универсалните препратки“ се появяват само когато типът на изведен параметър е 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& вашето заключение не е валидно, поне е двусмислено. Можете ли да изясните какво се опитвате да кажете, като казвате, че те само се появяват и демонстрирате, че препращането на препратки не е супер магия? - person Yakk - Adam Nevraumont; 01.06.2015
comment
@Yakk Доколкото ми е известно, универсална (или препращаща) препратка е името, дадено на пресечната точка на дедукция на тип, препратки към rvalue и свиване на препратка. Вярно е, че не взех под внимание случая, когато самото T е препратка. Все още ли е референция за препращане, ако препратеният обект е винаги от референтен тип? - person Quentin; 01.06.2015
comment
Идеята на Yakk е, че ако T е int&, arg също ще бъде от тип int&, т.е. правилата за свиване на препратки ще се прилагат, но това не е препращаща препратка, защото не е в изведен контекст. - person TartanLlama; 01.06.2015
comment
И сравнително стандартна употреба е да се изведе тип в тип. Използвайте помощна функция, за да произведете T в дедукция, инстанцирайте foo_t<T>. Разделя приспадането от употреба, но... - person Yakk - Adam Nevraumont; 01.06.2015