Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I expand call to variadic template base classes?

I have a set of non-orthogonal policies, all of them implementing a common named method, the policies add safety checks. I want users to be able to combine the policies to allow more complex validation without creating policies for each combination case by hand. My approach is creating a new policy class to combine others.

The simplified example below shows C as the combining class, here the method id is combined. The expected result is, when calling id on C, to sequentially call the id of each base class.

#include <iostream>
using namespace std;

struct A 
{
    void id() { cout << "A ";}
};

struct B 
{
    void id() { cout << "B ";}
};

template<class A, class... As>
struct C : public A, public As... 
{
    void id()
    {
         A::id();
         As...::id(); // This line does not work, it is illustrative.
    }
};

int main()
{
    C<A, B> c;
    c.id();

    //expected: result A B 
}

The question is: Is it possible to expand As... somehow to do this without using a recursive approach, just using the ... operator?

like image 979
dvicino Avatar asked May 31 '15 22:05

dvicino


People also ask

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is parameter pack in c++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.

What is Pack expansion?

Pack expansion A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order. template<class...


1 Answers

Sure. You need a context that permits pack expansion - a simple one is a braced initializer list, which also has the benefit of guaranteeing left-to-right evaluation:

using expander = int[];
(void) expander { 0, ((void) As::id(), 0)... };
  • ... expands a pattern to its left; in this case the pattern is the expression ((void) As::id(), 0).

  • The , in the expression is the comma operator, which evaluates the first operand, discards the result, then evaluates the second operand, and returns the result.

  • The (void) cast on As::id() exists to guard against overloaded operator,, and can be omitted if you are sure that none of the As::id() calls will return something that overloads the comma operator.
  • 0 on the right hand side of the comma operator is because expander is an array of ints, so the whole expression (which is used to initialize an element of the array) must evaluate to an int.
  • The first 0 ensures that we don't attempt to create an illegal 0-sized array when As is an empty pack.

Demo.


In C++17 (if we are lucky), the entire body of C::id can be replaced with a binary fold expression: (A::id(), ... , (void) As::id()); Demo.

like image 190
T.C. Avatar answered Sep 18 '22 22:09

T.C.