Variadisk mall

Inom datorprogrammering är variadiska mallar mallar som tar ett varierande antal argument.

Variadiska mallar stöds av C++ (sedan C++11 -standarden) och programmeringsspråket D .

C++

Den variatiska mallfunktionen i C++ designades av Douglas Gregor och Jaakko Järvi och standardiserades senare i C++11. Före C++11 kunde mallar (klasser och funktioner) bara ta ett fast antal argument, som måste specificeras när en mall först deklarerades. C++11 tillåter malldefinitioner att ta ett godtyckligt antal argument av vilken typ som helst.

                   mall  <  typnamn  ...  Värden  >  klass  tupel  ;  // tar noll eller fler argument 

Ovanstående mallklasstuppel tar valfritt antal typnamn som mallparametrar. Här instansieras en instans av ovanstående mallklass med tre typargument:

     tuple  <  int  ,  std  ::  vektor  <  int  >  ,  std  ::  map  <  std  ::  string  ,  std  ::  vektor  <  int  >>>  some_instance_name  ; 

Antalet argument kan vara noll, så tuplea <> some_instance_name ; kommer också att fungera.

Om den variatiska mallen endast ska tillåta ett positivt antal argument, kan denna definition användas:

       mall  <  typnamn  Först  typnamn  ...  Rest  >  klass  tupel  ;  _  // tar ett eller flera argument 

Variadiska mallar kan också tillämpas på funktioner, vilket inte bara tillhandahåller ett typsäkert tillägg till variadicfunktioner (som printf), utan tillåter också en funktion som anropas med printf-liknande syntax för att bearbeta icke-triviala objekt.

        mall  <  typnamn  ...  Params  >  void  my_printf  (  const  std  ::  string  &  str_format  ,  Params  ...  parametrar  ); 

Ellipsoperatorn (... ) har två roller. När det förekommer till vänster om namnet på en parameter, deklarerar det ett parameterpaket. Med hjälp av parameterpaketet kan användaren binda noll eller fler argument till de variatiska mallparametrarna. Parameterpaket kan också användas för parametrar som inte är av typen. Däremot, när ellipsoperatorn förekommer till höger om ett mall- eller funktionsanropsargument, packar den upp parameterpaketen i separata argument, som args... i huvuddelen av printf nedan. I praktiken gör användningen av en ellipsoperator i koden att hela uttrycket som föregår ellipsen upprepas för varje efterföljande argument som packas upp från argumentpaketet, med uttrycken separerade med kommatecken.

Användningen av variadiska mallar är ofta rekursiv. De variatiska parametrarna i sig är inte lätt tillgängliga för implementering av en funktion eller klass. Därför skulle den typiska mekanismen för att definiera något som en C++11 variadic printf- ersättning vara som följer:


   

     
    
           
        
                 
                
            
                 
        

          
    



   
       

     
    
           
        
                 
            
                
                  
                
                  
                
                 
                
            

            
        

          
        
 // base case  void  my_printf  (  const  char  *  s  )  {  while  (  *  s  )  {  if  (  *  s  ==  ' % '  )  {  if  (  *  (  s  +  1  )  !=  ' % '  )  ++  s  ;  else  throw  std  ::  runtime_error  (  "ogiltig formatsträng: saknade argument"  );  }  std  ::  cout  <<  *  s  ++  ;  }  }  // rekursiv  mall  <  typnamn  T  ,  typnamn  ...  Args  >  void  my_printf  (  const  char  *  s  ,  T  value  ,  Args  ...  args  )  {  while  (  *  s  )  {  if  (  *  s  ==  '%'  )  {  if  (  *  (  s  +  1  )  !=  '%'  )  {  // låtsas tolka formatet: fungerar endast på strängar med två tecken ( %d, %f, etc ); misslyckas med %5.4f   s  +=  2  ;  // skriv ut värdet  std  ::  cout  <<  värde  ;  // anropas även när *s är 0 men gör ingenting i så fall (och ignorerar extra argument)  my_printf  (  s  ,  args  ...);  återvända  ;  }  ++  s  ;  }  std  ::  cout  <<  *  s  ++  ;  }  } 

Detta är en rekursiv mall. Lägg märke till att den variadiska mallversionen av my_printf anropar sig själv, eller (i händelse av att args... är tom) anropar basfallet.

Det finns ingen enkel mekanism för att iterera över värdena för den variadiska mallen. Det finns dock flera sätt att översätta argumentpaketet till ett enda argument som kan utvärderas separat för varje parameter. Vanligtvis kommer detta att förlita sig på funktionsöverbelastning , eller - om funktionen helt enkelt kan välja ett argument i taget - med hjälp av en dum expansionsmarkör:

      mall  <  typnamn  ...  Args  >  inline  void  pass  (  Args  &&  ...)  {} 

som kan användas enligt följande:

     

    


   mall  <  typnamn  ...  Args  >  inline  void  expand  (  Args  &&  ...  args  )  {  pass  (  some_function  (  args  )...);  }  expand  (  42  ,  "svar"  ,  sant  ); 

som kommer att expandera till något som:

         pass  (  some_function  (  arg1  ),  some_function  (  arg2  ),  some_function  (  arg3  )  /* etc... */  ); 

Användningen av denna "pass"-funktion är nödvändig, eftersom expansionen av argumentpaketet fortsätter genom att separera funktionsanropsargumenten med kommatecken, som inte är likvärdiga med kommaoperatorn. Därför, some_function(args)...; kommer aldrig att fungera. Dessutom kommer lösningen ovan bara att fungera när returtypen för some_function inte är ogiltig . Dessutom some_function- anropen att exekveras i en ospecificerad ordning, eftersom ordningen för utvärdering av funktionsargument är odefinierad. För att undvika den ospecificerade ordningen kan initieringslistor med parenteser användas, vilket garanterar en strikt utvärderingsordning från vänster till höger. En initialiseringslista kräver en returtyp utan tomrum , men kommaoperatorn kan användas för att ge 1 för varje expansionselement.

 

       


  struct  pass  {  mall  <  typnamn  ...  T  >  pass  (  T  ...)  {}  };  pass  {(  some_function  (  args  ),  1  )...}; 

Istället för att köra en funktion kan ett lambda-uttryck specificeras och exekveras på plats, vilket tillåter exekvering av godtyckliga sekvenser av satser på plats.

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

Men i det här exemplet är en lambdafunktion inte nödvändig. Ett mer vanligt uttryck kan användas istället:

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

Ett annat sätt är att använda överbelastning med "termineringsversioner" av funktioner. Detta är mer universellt, men kräver lite mer kod och mer ansträngning för att skapa. En funktion tar emot ett argument av någon typ och argumentpaketet, medan den andra inte tar emot någotdera. (Om båda hade samma lista med initiala parametrar, skulle anropet vara tvetydigt - ett variadisk parameterpaket kan inte ensamt göra ett anrop disambiguera.) Till exempel:

   

   
      

      
     
 void  func  ()  {}  // uppsägningsversion  mall  <  typnamn  Arg1  ,  typnamn  ...  Args  >  void  func  (  const  Arg1  &  arg1  ,  const  Args  &&  ...  args  )  {  process  (  arg1  );  func  (  args  ...);  // notera: arg1 visas inte här!  } 

Om args... innehåller minst ett argument, kommer det att omdirigera till den andra versionen — ett parameterpaket kan vara tomt, i vilket fall det helt enkelt omdirigerar till avslutningsversionen, vilket inte gör någonting.

Variadiska mallar kan också användas i en undantagsspecifikation, en basklasslista eller initieringslistan för en konstruktor. Till exempel kan en klass ange följande:

  
    


      
         
    
 mall  <  typnamn  ...  Basklasser  >  klass  Klassnamn  :  offentliga  Basklasser  ...  {  offentliga  :  Klassnamn  (  Basklasser  &&  ...  basklasser  )  :  Basklasser  (  basklasser  )...  {}  }; 

Uppackningsoperatorn kommer att replikera typerna för basklasserna för ClassName , så att denna klass kommer att härledas från var och en av de typer som skickas in. Dessutom måste konstruktorn ta en referens till varje basklass för att initiera basklasserna för Klassnamn .

När det gäller funktionsmallar kan de variatiska parametrarna vidarebefordras. I kombination med universella referenser (se ovan) möjliggör detta perfekt vidarebefordran:

 
 

     
      
    
          
    
 mall  <  typnamn  TypeToConstruct  >  struct  SharedPtrAllocator  {  mall  <  typnamn  ...  Args  >  std  ::  shared_ptr  <  TypeToConstruct  >  construct_with_shared_ptr  (  Args  &&  ...  params  )  {  return  std  ::  shared_ptr  <  TypeToConstruct  >  (  new  TypeToConstruct  (  std  ::  forward  <  Args  >  (  params  )...));  }  }; 

Detta packar upp argumentlistan i konstruktorn för TypeToConstruct. Std ::forward<Args>(params) -syntaxen vidarebefordrar argument perfekt som deras rätta typer, även med avseende på rvalue-ness, till konstruktorn. Uppackningsoperatören kommer att sprida vidarebefordransyntaxen till varje parameter. Denna speciella fabriksfunktion lindar automatiskt det tilldelade minnet i en std::shared_ptr för en viss säkerhet när det gäller minnesläckor.

Dessutom kan antalet argument i ett mallparameterpaket bestämmas enligt följande:

 
 

         
 mall  <  typnamn  ...  Args  >  struct  SomeStruct  {  static  const  int  size  =  sizeof  ...(  Args  );  }; 

Uttrycket SomeStruct<Type1, Type2>::size kommer att ge 2, medan SomeStruct<>::size ger 0.

D

Definition

Definitionen av variadisk mall i D liknar deras C++ motsvarighet:

     mall  VariadicTemplate  (  Args  ...)  {  /* Body */  } 

På samma sätt kan alla argument föregå argumentlistan:

          mall  VariadicTemplate  (  T  ,  strängvärde  ,  aliassymbol  ,  Args  ...)  {  /  * Body *  /  } 

Grundläggande användning

Variadiska argument påminner mycket om konstant array i deras användning. De kan upprepas, nås av ett index, har en length- egenskap och kan delas upp . Operationer tolkas vid kompilering, vilket innebär att operander inte kan vara ett körtidsvärde (som funktionsparametrar).

Allt som är känt vid kompilering kan skickas som ett variadisk argument. Det gör variatiska argument som liknar mallaliasargument , men mer kraftfulla, eftersom de också accepterar grundläggande typer (char, short, int...).

Här är ett exempel som skriver ut strängrepresentationen av de variatiska parametrarna. StringOf och StringOf2 ger lika resultat.

  

  

 

       
       


 

     0  


 

     


 

      0
       
  
       0  
 statisk  int  s_int  ;  struct  Dummy  {}  void  main  ()  {  pragma  (  msg  ,  StringOf  !(  "Hello world"  ,  uint  ,  Dummy  ,  42  ,  s_int  ));  pragma  (  msg  ,  StringOf2  !(  "Hej värld"  ,  uint  ,  Dummy  ,  42  ,  s_int  ));  }  mall  StringOf  (  Args  ...)  {  enum  StringOf  =  Args  [  ].  stringof  ~  StringOf  !(  Args  [  1.  .$]);  }  mall  StringOf  ()  {  enum  StringOf  =  ""  ;  }  mall  StringOf2  (  Args  ...)  {  static  if  (  Args  .  length  ==  )  enum  StringOf2  =  ""  ;  annars  enum  StringOf2  =  Args  [  ].  stringof  ~  StringOf2  !(  Args  [  1.  .$]);  } 

Utgångar:

"Hej värld"uintDummy42s_int "Hej värld"uintDummy42s_int

AliasSeq

Variadiska mallar används ofta för att skapa en sekvens av alias, som heter AliasSeq . Definitionen av en AliasSeq är faktiskt väldigt enkel:

    alias  AliasSeq  (  Args  ...)  =  Args  ; 

Den här strukturen tillåter en att manipulera en lista med olika argument som automatiskt expanderar. Argumenten måste antingen vara symboler eller värden kända vid kompileringstillfället. Detta inkluderar värden, typer, funktioner eller till och med icke-specialiserade mallar. Detta tillåter alla operationer du kan förvänta dig:

 

 

  
          
  
         
       
  
     0    
     0         
  
      
     0    


  

     0    
 import  std  .  meta  ;  void  main  ()  {  // Obs: AliasSeq kan inte modifieras och ett alias kan inte rebound, så vi måste definiera nya namn för våra modifieringar.  aliasnummer  =  AliasSeq  !(  1  ,  2  ,  3  ,  4  ,  5  ,  6  )  ;  // Slicing  alias  lastHalf  =  nummer  [$  /  2  ..  $];  static  assert  (  lastHalf  ==  AliasSeq  !(  4  ,  5  ,  6  ));  // AliasSeq automatisk expansion  alias  siffror  =  AliasSeq  !(  ,  siffror  ,  7  ,  8  ,  9  );  statisk  påstående  (  siffror  ==  AliasSeq  !(  ,  1  ,  2  ,  3  ,  4  ,  5  ,  6  ,  7  ,  8  ,  9  ));  // std.meta tillhandahåller mallar för att fungera med AliasSeq, såsom anySatisfy, allSatisfy, staticMap och Filter.  alias  evenNumbers  =  Filter  !(  isEven  ,  siffror  );  static  assert  (  evenNumbers  ==  AliasSeq  !(  ,  2  ,  4  ,  6  ,  8  ));  }  mall  isEven  (  int  number  )  {  enum  isEven  =  (  ==  (  nummer  %  2  ));  } 

Se även

För artiklar om andra variatiska konstruktioner än mallar

externa länkar