Jakie są zasady dotyczące tokena „…” w kontekście różnych szablonów?

98

W C ++ 11 istnieją różne szablony, takie jak ten:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Jest kilka ciekawostek na ten temat: wyrażenie std::forward<Args>(args)...używa obu Argsi argstylko jednego ...tokenu. Ponadto std::forwardjest to niezmienna funkcja szablonu, która pobiera tylko jeden parametr szablonu i jeden argument. Jakie są dla tego reguły składni (w przybliżeniu)? Jak można to uogólnić?

Ponadto: W implementacji funkcji wielokropek ( ...) znajduje się na końcu wyrażenia zainteresowania. Czy istnieje powód, dla którego na liście argumentów szablonu i liście parametrów wielokropek znajduje się pośrodku?

Ralph Tandetzky
źródło
2
Krótko o drugiej części: „Deklarowanie” pakietu parametrów szablonu lub pakietu parametrów funkcji ...następuje przed wprowadzeniem identyfikatora. W przypadku korzystania z jednego lub obu typów pakietów następuje ...rozwinięcie po wzorcu wyrażenia.
aschepler

Odpowiedzi:

99

W kontekście szablonu wariadycznego wielokropek ...służy do rozpakowywania pakietu parametrów szablonu, jeśli pojawia się on po prawej stronie wyrażenia (wywołaj na chwilę ten wzorzec wyrażenia ). Zasada jest taka, że ​​każdy wzorzec znajdujący się po lewej stronie ...jest powtarzany - rozpakowane wzorce (nazwijmy je teraz wyrażeniami ) są oddzielane przecinkiem ,.

Najlepiej można to zrozumieć na kilku przykładach. Załóżmy, że masz ten szablon funkcji:

template<typename ...T>
void f(T ... args) 
{
   g( args... );        //pattern = args
   h( x(args)... );     //pattern = x(args)
   m( y(args...) );     //pattern = args (as argument to y())
   n( z<T>(args)... );  //pattern = z<T>(args)
}

Jeśli teraz wywołam tę funkcję przekazując Tjako {int, char, short}, to każde wywołanie funkcji jest interpretowane jako:

g( arg0, arg1, arg2 );           
h( x(arg0), x(arg1), x(arg2) );
m( y(arg0, arg1, arg2) );
n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );

W opublikowanym kodzie std::forwardnastępuje czwarty wzorzec zilustrowany n()wywołaniem funkcji.

Zwróć uwagę na różnicę między x(args)...i y(args...)powyżej!


Możesz użyć ...do zainicjowania tablicy również jako:

struct data_info
{
     boost::any  data;
     std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

który jest rozszerzony do tego:

std::vector<data_info> v 
{ 
   {arg0, sizeof(int)},
   {arg1, sizeof(char)},
   {arg2, sizeof(short)}
};

Właśnie zdałem sobie sprawę, że wzorzec może nawet zawierać specyfikator dostępu, taki publicjak pokazano w poniższym przykładzie:

template<typename ... Mixins>
struct mixture : public Mixins ...  //pattern = public Mixins
{
    //code
};

W tym przykładzie wzorzec jest rozwijany jako:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

Oznacza to, że mixturedziedziczy publicznie ze wszystkich klas podstawowych.

Mam nadzieję, że to pomoże.

Nawaz
źródło
1
Jeśli chodzi o wyrażenie, które jest dopasowane; Powinien to być możliwie największy wyraz, prawda? Na przykład x+args...należy rozszerzyć do x+arg0,x+arg1,x+arg2, a nie x+arg0,arg1,arg2.
maska ​​bitowa
Tak więc ...odnosi się do każdej rozwijalnej jednostki we wzorcu.
Wyścigi lekkości na orbicie,
@bitmask: Tak. x+args...powinien się rozszerzyć x+arg0,x+arg1,x+arg2, a nie x+arg0,arg1,arg2 .
Nawaz
1
@ Jarod42: Zaktualizuję tę odpowiedź po wydaniu C ++ 17.
Nawaz
3
@synther: sizeof...(T)nie jest tam potrzebne. Możesz po prostu napisać:int a[] = { ___ };
Nawaz
48

Poniższy fragment pochodzi z wykładu „Variadic Templates are Funadic” wygłoszonego przez Andrei Alexandrescu na GoingNative 2012. Mogę go polecić jako dobre wprowadzenie do wariadycznych szablonów.


Są dwie rzeczy, które można zrobić z pakietem wariadycznym. Można zastosować, sizeof...(vs)aby uzyskać liczbę elementów i ją rozwinąć.

Zasady rozbudowy

Use            Expansion

Ts...          T1, ..., Tn
Ts&&...        T1&&, ..., Tn&&
x<Ts,Y>::z...  x<T1,Y>::z, ..., x<Tn,Y>::z
x<Ts&,Us>...   x<T1&,U1>, ..., x<Tn&,Un>
func(5,vs)...  func(5,v1), ..., func(5,vn)

Ekspansja postępuje do wewnątrz na zewnątrz. Przy rozszerzaniu dwóch list w trybie lock-step muszą one mieć ten sam rozmiar.

Więcej przykładów:

gun(A<Ts...>::hun(vs)...);

Rozwija wszystko Tsna liście argumentów szablonu, Aa następnie funkcja hunzostaje rozszerzona o all vs.

gun(A<Ts...>::hun(vs...));

Rozwija all Tsna liście argumentów szablonu Ai all vsjako argumenty funkcji dla hun.

gun(A<Ts>::hun(vs)...);

Rozszerza funkcję za hunpomocą Tsiw trybie vslock-step.

Uwaga:

Tsnie jest typem i vsnie jest wartością! Są to aliasy listy typów / wartości. Każda lista może być potencjalnie pusta. Obaj wykonują tylko określone czynności. Więc nie jest możliwe:

typedef Ts MyList;  // error!
Ts var;             // error!
auto copy = vs;     // error!

Rozwinięcie loci

Argumenty funkcji

template <typename... Ts>
void fun(Ts... vs)

Listy inicjalizujące

any a[] = { vs... };

Podstawowe specyfikatory

template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };

Listy inicjatorów członków

// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}

Utwórz tymczasowe listy argumentów

std::map<Ts...> m;

Kompiluje się tylko wtedy, gdy istnieje możliwość dopasowania argumentów.

Listy przechwytywania

template <class... Ts> void fun(Ts... vs) {
    auto g = [&vs...] { return gun(vs...); }
    g();
}

Listy atrybutów

struct [[ Ts... ]] IAmFromTheFuture {};

Jest w specyfikacji, ale nie ma jeszcze atrybutu, który można wyrazić jako typ.

typ1232
źródło
Miły. Jednak argumenty funkcji są
pomijane
@Potatoswatter Thanks. Nie wspomniano o rozwinięciu listy argumentów funkcji.
typ1232
@ typ1232 Przepraszamy za zmianę, ale czułem, że Twój oryginalny post był zbyt bliski plagiatu. Przy okazji, oglądałem też tę rozmowę jakiś czas temu - super.
Walter