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.
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&&)
{ }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With