Variadic Templates example

后端 未结 5 2174
别那么骄傲
别那么骄傲 2021-02-07 20:20

Consider following code, I don\'t understand why empty function of print must be defined.

#include 
using namespace std;

void print()
{   
}   
         


        
相关标签:
5条回答
  • 2021-02-07 20:41

    Recursion is the most general way to program variadic templates, but it's far from the only way. For simple use cases like this, doing a pack expansion directly within a braced initializer list is shorter and likely faster to compile.

    template <typename... Types>
    void print (const Types&... args)
    {   
        using expander = int[];
        (void) expander { 0, (void(cout << args << endl), 0) ...};
    }
    

    In C++17, we'll be able to use a fold expression:

    template <typename... Types>
    void print (const Types&... args)
    {   
        (void(cout << args << endl) , ...);
    }
    
    0 讨论(0)
  • 2021-02-07 20:46

    If you don't have empty print function, imagine a call with 2 parameters :

    1. print (a, b) => cout << a << endl and call print(b)
    2. print (b) => cout << b << endl and call print()

    oups, print() doesn't exists, because only print with at least one parameter exists ! So you need a print without parameters.

    print without any parameters is your final call

    0 讨论(0)
  • 2021-02-07 20:49

    To add on to the other answers, I would like to show what the compiler has to generate for the template calls.

    nm -g -C ./a.out (non-optimized build) gives:

    void print<char [5]>(char const (&) [5])
    void print<char [5]>(char const (&) [5])
    void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
    void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
    void print<char, char [5]>(char const&, char const (&) [5])
    void print<char, char [5]>(char const&, char const (&) [5])
    void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
    void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
    void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
    void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
    void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
    void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
    print()
    

    You can see all the instantiations of the print function. The final function that ultimately calls print() is void print<char [5]>(char const (&) [5])>

    You can see that when passed an empty parameter pack, the template parameter list must necessarily be empty. Therefore it just calls print(). If you explicitly specified the template parameters, like print<T, Args...>(t, args...) you would get infinite recursion.

    0 讨论(0)
  • 2021-02-07 20:51

    Because (using a very "simple" level of explanation) the variadic templates mechanism works like a recursion (it is NOT recursion but this is the simplest way to comprehend it), which "consumes" the variadic parameter list, so you will have to define a "stop" function, at which it will recur in the last step of recursion when the "consumed" parameter list is "empty". This was the explanation that I found the easiest to understand this pretty complex notion.

    At the very first step (in main) you will get a function which has the parameters: (int, const char*, int, int, char, const char*)

    Then the variadic parameters are slowly being processed in the variadic function itself, leaving you in the second step with (const char*, int, int, char, const char*) then (int, int, char, const char*) and so on ... till you reach the last element (const char*), and when this is also processed in the next step you end up with (), and the compiler needs this function as a "terminator"

    (Yes, this is very non technical and sounds like grandpa frog telling a story to little froglings ...)

    0 讨论(0)
  • 2021-02-07 20:55

    CORRECT WAY:

    Variadic templates is strictly related to induction, a mathematical concept.

    The compiler resolves the following function call

    print('a', 3, 4.0f);
    

    into

    std::cout<< 'a' <<std::endl;
    print(3, 4.0f);
    

    which is resolved into

    std::cout<< 'a' <<std::endl;
    std::cout<< 3 <<std::endl;
    print( 4.0f);
    

    which is resolved into

    std::cout<< 'a' <<std::endl;
    std::cout<< 3 <<std::endl;
    std::cout<< 4.0f <<std::endl;
    print();
    

    At this point it searches for a function overload whose match is the empty function.

    • All functions that have 1 or more arguments are matched to the variadic template
    • All functions that have no argument are matched to the empty function

    The culprit is that you must have, for every possible combination of parameters, only 1 function.


    ERROR 1:

    Doing the following would be an error

    template< typename T>
    void print( const T& arg) // first version
    {   
        cout<< arg<<endl;
    }   
    
    template <typename T, typename... Types>
    void print (const T& firstArg, const Types&... args) // second version
    {   
        cout << firstArg << endl; // print first argument
        print(args...); // call print() for remaining arguments
    }
    

    Because when you call print the compiler doesn't know which function to call.

    Does print(3) refers to "first" or "second" version? Both would be valid because the first has 1 parameter, and the second can accept one parameter too.

    print(3); // error, ambiguous, which one you want to call, the 1st or the 2nd?
    

    ERROR 2:

    The following would be an error anyway

    // No empty function
    
    template <typename T, typename... Types>
    void print (const T& firstArg, const Types&... args) 
    {   
        cout << firstArg << endl; // print first argument
        print(args...); // call print() for remaining arguments
    }
    

    In fact, if you use it alone without the compiler would do

     print('k', 0, 6.5);
    

    which is resolved into

     std::cout<<'k'<<std::endl;
     print(0, 6.5);
    

    which is resolved into

     std::cout<<'k'<<std::endl;
     std::cout<< 0 <<std::endl;
     print( 6.5);
    

    which is resolved into

     std::cout<<'k'<<std::endl;
     std::cout<< 0 <<std::endl;
     std::cout<< 6.5 <<std::endl;
     print(); //Oops error, no function 'print' to call with no arguments
    

    As you see in the last attempt, the compiler tries to call print() with no arguments. However if such a function does not exists it is not called, and that's why you should provide that empty function (don't worry, the compiler will optimize code so empty functions don't decrease performance).

    0 讨论(0)
提交回复
热议问题