Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameter Pack with alternating types

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.

like image 882
PeWe Avatar asked Feb 04 '23 20:02

PeWe


2 Answers

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.

like image 173
palotasb Avatar answered Feb 06 '23 10:02

palotasb


I suppose you can use a template delegating constructor

Something as follows

#include <utility>

struct A {};
struct B {};

struct C
 {
   C (A &&)
    { }

   template <typename ... Ts>
   C (A &&, B &&, Ts && ... ts) : C(std::forward<Ts>(ts)...)
    { }
 };

int main()
 {
   C(A{});
   C(A{}, B{}, A{});
   C(A{}, B{}, A{}, B{}, A{});
   C(A{}, B{}, A{}, B{}, A{}, B{}, A{});
 }

If you require at least three element (so no C(A{}) but at least C(A{}, B{}, A{})) the not-template constructor become

   C (A &&, B &&, A&&)
    { }
like image 35
max66 Avatar answered Feb 06 '23 10:02

max66