Szablon wariadyczny

W programowaniu komputerowym szablony variadic to szablony , które przyjmują zmienną liczbę argumentów.

Szablony Variadic są obsługiwane przez C++ (od standardu C++11 ) oraz język programowania D.

C++

Wariantowa funkcja szablonu C++ została zaprojektowana przez Douglasa Gregora i Jaakko Järviego, a później została znormalizowana w C++11. Przed C++11 szablony (klasy i funkcje) mogły przyjmować tylko ustaloną liczbę argumentów, które musiały być określone podczas pierwszej deklaracji szablonu. C++11 pozwala definicjom szablonów przyjmować dowolną liczbę argumentów dowolnego typu.

                   szablon  <  nazwa typu  ...  Wartości  >  krotka  klasy  ;  // pobiera zero lub więcej argumentów 

Powyższa krotka klasy szablonu przyjmie dowolną liczbę nazw typów jako parametry szablonu. Tutaj instancja powyższej klasy szablonu jest tworzona z trzema argumentami typu:

     krotka  <  int  ,  std  ::  vector  <  int  >  ,  std  ::  map  <  std  ::  string  ,  std  ::  vector  <  int  >>>  nazwa_instancji  ; 

Liczba argumentów może wynosić zero, więc krotka <> nazwa_instancji ; też zadziała.

Jeśli szablon wariadyczny powinien zezwalać tylko na dodatnią liczbę argumentów, można zastosować tę definicję:

       szablon  <  nazwa typu  Najpierw  nazwa  typu  ...  Reszta  >  krotka  klasy  ;  // przyjmuje jeden lub więcej argumentów 

Szablony variadic mogą również odnosić się do funkcji, zapewniając w ten sposób nie tylko bezpieczny dodatek do funkcji variadic (takich jak printf), ale także umożliwiając funkcji wywoływanej ze składnią podobną do printf do przetwarzania nietrywialnych obiektów.

        szablon  <  nazwa typu  ...  Parametry  >  void  my_printf  (  const  std  ::  string  &  str_format  ,  Params  ...  parametry  ); 

wielokropka ( ...) ma dwie role. Gdy występuje po lewej stronie nazwy parametru, oznacza to pakiet parametrów. Korzystając z pakietu parametrów, użytkownik może powiązać zero lub więcej argumentów z parametrami szablonu variadic. Pakiety parametrów mogą być również używane dla parametrów innych niż typ. Z drugiej strony, gdy operator wielokropka pojawia się po prawej stronie szablonu lub argumentu wywołania funkcji, rozpakowuje pakiety parametrów do oddzielnych argumentów, takich jak argumenty ... w treści printf poniżej. W praktyce użycie operatora wielokropka w kodzie powoduje, że całe wyrażenie poprzedzające wielokropek jest powtarzane dla każdego kolejnego argumentu wypakowanego z pakietu argumentów, z wyrażeniami oddzielonymi przecinkami.

Stosowanie szablonów wariadycznych jest często rekurencyjne. Same parametry zmiennoprzecinkowe nie są łatwo dostępne dla implementacji funkcji lub klasy. Dlatego typowy mechanizm definiowania czegoś w rodzaju zamiany variadic printf w C++ 11 wyglądałby następująco:


   

     
    
           
        
                 
                
            
                  // przypadek podstawowy  void  my_printf  (  const  char  *  s  )  {  while  (  *  s  )  {  if  (  *  s  ==  '%'  )  {  if  (  *  (  s  +  1  )  !=  '%'  )  ++  s  ;  w przeciwnym razie  wyrzuć  std  ::  runtime_error  ( 
        

          
    



   
       

      "nieprawidłowy łańcuch formatu: brak argumentów"  );  }  std  ::  cout  <<  *  s  ++  ;  }  } //   szablon  rekurencyjny  <  nazwa typu  T  ,  nazwa typu  ...  Args  >  void  my_printf  (  const  char  *  s  ,  T  value  ,  Args  ...  args  )  {  while  (  * 
    
           
        
                 
            
                
                  
                
                   s  )  {  if  (  *  s  ==  '%'  )  {  if  (  *  (  s  +  1  )  !=  '%'  )  {  // udawaj, że przetwarzasz format: działa tylko na ciągach znaków składających się z 2 znaków ( %d, % f itd.); kończy się niepowodzeniem z %5.4f   s  +=  2  ;  // wypisz wartość  std  ::  cout  <<  wartość  ; 
                
                 
                
            

            
        

          
        
 // wywoływana nawet wtedy, gdy *s wynosi 0, ale w takim przypadku nic nie robi (i ignoruje dodatkowe argumenty)  my_printf  (  s  ,  args  ...);  powrót  ;  }  ++  s  ;  }  std  ::  cout  <<  *  s  ++  ;  }  } 

To jest szablon rekurencyjny. Zauważ, że zmienna wersja szablonu my_printf wywołuje samą siebie lub (w przypadku, gdy args... jest pusta) wywołuje przypadek podstawowy.

Nie ma prostego mechanizmu do iteracji po wartościach szablonu variadic. Istnieje jednak kilka sposobów przetłumaczenia pakietu argumentów na pojedynczy argument, który można ocenić osobno dla każdego parametru. Zwykle polega to na przeciążaniu funkcji lub — jeśli funkcja może po prostu wybrać jeden argument naraz — użyciu głupiego znacznika rozszerzenia:

      szablon  <  nazwa typu  ...  Args  >  inline  void  pass  (  Args  &&  ...)  {} 

które można wykorzystać w następujący sposób:

     

    


   szablon  <  nazwa typu  ...  Args  >  inline  void  expand  (  Args  &&  ...  args  )  {  pass  (  some_function  (  args  )...);  }  rozwiń  (  42  ,  "odpowiedź"  ,  prawda  ); 

który rozwinie się do czegoś takiego:

         pass  (  jakaś_funkcja  (  arg1  ),  jakaś_funkcja  (  arg2  ),  jakaś_funkcja  (  arg3  )  /* itd... */  ); 

Użycie tej funkcji „pass” jest konieczne, ponieważ rozwinięcie pakietu argumentów odbywa się poprzez oddzielenie argumentów wywołania funkcji przecinkami, które nie są równoważne operatorowi przecinka. Dlatego jakaś_funkcja(argumenty)...; nigdy nie zadziała. Co więcej, powyższe rozwiązanie będzie działać tylko wtedy, gdy typem zwracanym przez some_function nie jest void . Ponadto funkcja some_function wywołania będą wykonywane w nieokreślonej kolejności, ponieważ kolejność obliczania argumentów funkcji jest niezdefiniowana. Aby uniknąć nieokreślonej kolejności, można użyć list inicjalizacyjnych ujętych w nawiasy klamrowe, które gwarantują ścisłą kolejność oceny od lewej do prawej. Lista inicjalizacyjna wymaga zwracanego typu innego niż void , ale można użyć operatora przecinka, aby uzyskać 1 dla każdego elementu rozszerzenia.

 

       


  struct  pass  {  szablon  <  nazwa typu  ...  T  >  pass  (  T  ...)  {}  };  przekazać  {(  jakaś_funkcja  (  argumenty  ),  1  )...}; 

Zamiast wykonywania funkcji można określić wyrażenie lambda i wykonać je w miejscu, co pozwala na wykonanie dowolnej sekwencji instrukcji w miejscu.

pass{([&](){ std::cout << argumenty << std::endl; }(), 1)...};

Jednak w tym konkretnym przykładzie funkcja lambda nie jest konieczna. Zamiast tego można użyć bardziej zwykłego wyrażenia:

pass{(std::cout << argumenty << std::endl, 1)...};

Innym sposobem jest użycie przeciążania z „wersjami zakończenia” funkcji. Jest to bardziej uniwersalne, ale wymaga trochę więcej kodu i więcej wysiłku w tworzeniu. Jedna funkcja otrzymuje jeden argument pewnego typu i pakiet argumentów, podczas gdy druga nie otrzymuje żadnego. (Gdyby oba miały tę samą listę parametrów początkowych, wywołanie byłoby niejednoznaczne — sam pakiet parametrów wariadycznych nie może ujednoznacznić wywołania). Na przykład:

   

   
      

      
     
 void  func  ()  {} //   szablon  wersji zakończenia  <  nazwa typu  Arg1  ,  nazwa typu  ...  Args  >  void  func  (  const  Arg1  &  arg1  ,  const  Args  &&  ...  args  )  {  process  (  arg1  );  funkcja  (  argumenty  ...);  // uwaga: arg1 nie pojawia się tutaj!  } 

Jeśli args... zawiera co najmniej jeden argument, przekieruje do drugiej wersji — pakiet parametrów może być pusty, w takim przypadku po prostu przekieruje do wersji zakończenia, która nic nie da.

Szablony Variadic mogą być również używane w specyfikacji wyjątków, liście klas podstawowych lub liście inicjalizacyjnej konstruktora. Na przykład klasa może określić następujące elementy:

  
    


      
         
    
 szablon  <  nazwa typu  ...  BaseClasses  >  class  ClassName  :  public  BaseClasses  ...  {  public  :  ClassName  (  BaseClasses  &&  ...  base_classes  )  :  BaseClasses  (  base_classes  )...  {}  }; 

Operator unpack zreplikuje typy dla klas podstawowych klasy ClassName , tak że ta klasa będzie pochodzić z każdego przekazanego typu. Ponadto konstruktor musi przyjąć odwołanie do każdej klasy podstawowej, aby zainicjować klasy podstawowe klasy nazwa klasy .

W odniesieniu do szablonów funkcji można przekazywać parametry zmienne. W połączeniu z uniwersalnymi referencjami (patrz wyżej) pozwala to na perfekcyjne przekierowanie:

 
 

     
      
    
          szablon  <  nazwa typu  TypeToConstruct  >  struct  SharedPtrAllocator  {  szablon  <  nazwa typu  ...  Args  >  std  ::  shared_ptr  <  TypeToConstruct  >  konstrukcja z_shared_ptr  (  Args  &&  ...  parametry  )  {  return  std  ::  shared_ptr  <  TypeToConstruct  >  (  nowy  
    
 TypeToConstruct  (  std  ::  forward  <  Args  >  (  params  )...));  }  }; 

Spowoduje to rozpakowanie listy argumentów do konstruktora TypeToConstruct. Składnia std::forward<Args>(params) doskonale przekazuje argumenty jako ich właściwe typy, nawet jeśli chodzi o rwartość, do konstruktora. Operator unpack propaguje składnię przekazywania do każdego parametru. Ta konkretna funkcja fabryczna automatycznie zawija przydzieloną pamięć w std::shared_ptr dla pewnego stopnia bezpieczeństwa w odniesieniu do wycieków pamięci.

Dodatkowo liczbę argumentów w pakiecie parametrów szablonu można określić w następujący sposób:

 
 

         
 szablon  <  nazwa typu  ...  Args  >  struct  SomeStruct  {  static  const  int  size  =  sizeof  ...(  Args  );  }; 

Wyrażenie SomeStruct<Type1, Type2>::size da 2, podczas gdy SomeStruct<>::size da 0.

D

Definicja

Definicja szablonów variadic w D jest podobna do ich odpowiednika w C++:

     szablon  VariadicTemplate  (  Args  ...)  {  /* Treść */  } 

Podobnie, każdy argument może poprzedzać listę argumentów:

          szablon  VariadicTemplate  (  T  ,  wartość  ciągu znaków  ,  symbol  aliasu  ,  argumenty  ...)  {  /* Body */  } 

Podstawowe użycie

Argumenty zmienne są bardzo podobne do stałych tablic w ich użyciu. Mogą być iterowane, dostępne przez indeks, mają length i mogą być dzielone . Operacje są interpretowane w czasie kompilacji, co oznacza, że ​​operandy nie mogą być wartościami w czasie wykonywania (takimi jak parametry funkcji).

Wszystko, co jest znane w czasie kompilacji, można przekazać jako argumenty wariadyczne. Sprawia, że ​​argumenty variadic są podobne do argumentów aliasów szablonu , ale są potężniejsze, ponieważ akceptują również podstawowe typy (char, short, int...).

Oto przykład, który drukuje reprezentację łańcuchową parametrów wariadycznych. StringOf i StringOf2 dają takie same wyniki.

  

  

 

       
        static  int  s_int  ;  struct  Dummy  {}  void  main  ()  {  pragma  (  msg  ,  StringOf  ! (  "Witaj świecie"  ,  uint  ,  Dummy  ,  42  ,  s_int  ));  pragma  (  msg  ,  StringOf2  !(  "Witaj świecie"  ,  uint  ,  Dummy  ,  42  ,  s_int  )); 


 

     0  


 

     


 

     }  szablon  StringOf  (  Argumenty  ...)  {  enum  StringOf  =  Args  [  ].  stringof  ~  StringOf  !(  Args  [  1.  .$]);  }  szablon  StringOf  ()  {  enum  StringOf  =  ""  ;  }  szablon  StringOf2  (  Args  ...)  {  static  if  (  Args  .  length   0
       
  
       0  
 ==  )  wyliczenie  StringOf2  =  ""  ;  w przeciwnym razie  wylicz  StringOf2  =  Args  [  ].  stringof  ~  StringOf2  !(  Args  [  1.  .$]);  } 

Wyjścia:

"Witaj świecie"uintDummy42s_int "Witaj świecie"uintDummy42s_int

AliasSeq

Szablony Variadic są często używane do tworzenia sekwencji aliasów o nazwie AliasSeq . Definicja AliasSeq jest w rzeczywistości bardzo prosta:

    alias  AliasSeq  (  Args  ...)  =  Args  ; 

Ta struktura pozwala manipulować listą zmiennych argumentów, które będą się automatycznie rozszerzać. Argumenty muszą być symbolami lub wartościami znanymi w czasie kompilacji. Obejmuje to wartości, typy, funkcje, a nawet niewyspecjalizowane szablony. Pozwala to na dowolną operację, jakiej można się spodziewać:

 

 

  
          
  
          import  standard  .  meta  ;  void  main  ()  {  // Uwaga: AliasSeq nie może być modyfikowany, a alias nie może być ponownie powiązany, więc będziemy musieli zdefiniować nowe nazwy dla naszych modyfikacji.  numery  aliasów  =  AliasSeq  !(  1  ,  2  ,  3  ,  4  ,  5  ,  6  );  // Krojenie  aliasu  lastHalf  =  liczby  [$  /  2  ..  $]; 
       
  
     0    
     0      static  assert  (  lastHalf  ==  AliasSeq  !(  4  ,  5  ,  6  ));  // AliasSeq automatyczne rozszerzenie  alias  digits  =  AliasSeq  !(  ,  numery  ,  7  ,  8  ,  9  );  static  assert  (  cyfry  ==  AliasSeq  !(  ,  1  ,  2  ,  3  ,  4  ,  5  ,     
  
      
     0    


  6  ,  7  ,  8  ,  9  ));  // std.meta udostępnia szablony do pracy z AliasSeq, takie jak anySatisfy, allSatisfy, staticMap i Filter.  alias  parzyste  =  Filtr  !(  isEven  ,  cyfry  );  static  assert  (  parzysteNumbers  ==  AliasSeq  !(  ,  2  ,  4  ,  6  ,  8  ));  }  szablon  isEven  (  int  

     0    
 liczba  )  {  enum  isEven  =  (  ==  (  liczba  %  2  ));  } 

Zobacz też

Artykuły dotyczące konstrukcji wariadycznych innych niż szablony

Linki zewnętrzne