Parameter Pack with alternating types

  • A+
Category:Languages

I have a struct C which gets initialized with a variable number of instances of struct A and struct B. E.g.:

struct A {};  struct B {};  struct C {     C(A&& o1, B&& p1, A&& o2)     {}     C(A&& o1, B&& p1, A&& o2, B&& p2, A&& o3)     {}     C(A&& o1, B&& p1, A&& o2, B&& p2, A&& o3, B&& p3, A&& o4)     {}     C(A&& o1, B&& p1, A&& o2, B&& p2, A&& o3, B&& p3, A&& o4, B&&p4, A&& o5)     {} }; 

So, rather than providing multiple ctor's with different number of parameters I would like to find something generic. However, the number of ctor parameters always grows about two parameters: B&& and A&&. Could this be accomplished using parameter packs. Or would be another solution without implementing for each number of parameters an according ctor?

The goal should be that struct C can be constructed like the following examples:

C c1 = { A(), B(), A() }; C c2 = { A(), B(), A(), B(), A(), B(), A() }; 

etc.

 


You can use a variadic template and SFINAE to enable only the constructor where the type parameters satisfy your (or any arbitrary) condition.

#include <type_traits> struct A {}; struct B {}; 

You need type_traits for std::false_type and std::true_type.

The alternates template is the key. The goal is to make alternates<X, Y, T1, T2, T3, ..., Tn> inherit from std::true_type if and only if the T1, ... Tn list is alternating X and Y. The default choice (just below) is no, but we specialize for matching cases.

template <typename X, typename Y, typename... Ts> struct alternates : std::false_type {}; 

I choose to make this template more generic than your requirement here and allow alternates<X, Y> to inherit from true_type. The empty list satisfies the mathematical requirement that all elements of it alternate. This will be a good stopgap for the recursive definition below.

template <typename X, typename Y> struct alternates<X, Y> : std::true_type {}; 

Any other list of alternates<X, Y, Ts...> alternates if and only if Ts... minus the first element alternate Y and X (Y first!).

template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};  struct C { 

We define the constructor as a template that first takes a parameter pack (the type will be deduced, no need to specify when calling) and it has a defaulted template parameter for SFINAE purposes. If the defaulted argument cannot be calculated based on the parameter pack, the constructor will not exist. I added the extra conditions about the number of pairs which I assumed from the example.

    template<typename... Ts,         typename = typename std::enable_if<             sizeof...(Ts) % 2 == 1 &&             sizeof...(Ts) >= 3 && // did you imply this?             alternates<A, B, Ts...>::value         >::type>     C(Ts&&...); }; 

The way SFINAE works is that std::enable_if only defines the std::enable_if<condition, T>::type (the ::type part) if condition is true. That can be any arbitrary boolean expression computable at compile time. If it is false, saying ::type at the end will be a substitution failure and the overload where you tried to use it (e.g., C{A(), A(), A()}) will simply not be defined.

You can test that the examples below work as expected. The ones commented out are not expected to work.

int main() {     C c1 { A(), B(), A() };     C c2 { A(), B(), A(), B(), A(), B(), A() };     // C c3 {};                     // I assumed you need at least 2     // C c4 { A(), B(), A(), A() }; // A, A doesn't alternate     // C c5 { B(), A(), B() };      // B, A, B not allowed     // C c6 { A(), B(), A(), B() }; // A, B, A, B doesn't pair } 

Try the code here.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: