Reputation: 99
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.
Upvotes: 9
Views: 672
Reputation: 7925
Maybe something like this would help...
#include <iostream>
#include <vector>
#include <utility>
// Simple classes A & B to represent your alternating pattern classes.
class A{ public: int a; };
class B{ public: int b; };
// helper class template to act as a single parameter kind of like std::pair...
template<typename T, typename U>
class Pack{
private:
T t_;
U u_;
public:
Pack( T&& t, U&& u ) :
t_( std::move( t ) ),
u_( std::move( u ) )
{}
T getT() const { return t_; }
U getU() const { return u_; }
};
// your class with varying amount of parameters for its ctors
template<class T, class U>
class C{
private:
std::vector<Pack<T,U>> packs_;
public:
template<typename... Packs>
C( Packs&&... packs ) : packs_{ std::move( packs )... } { }
std::vector<Pack<T,U>> getPacks() const {
return packs_;
}
};
// A few overloaded ostream operator<<()s for easy printing...
std::ostream& operator<<( std::ostream& os, const A& a ) {
os << a.a;
return os;
}
std::ostream& operator<<( std::ostream& os, const B& b ) {
os << b.b;
return os;
}
template<typename T, typename U>
std::ostream& operator<<( std::ostream& os, const Pack<T,U>& pack ) {
os << pack.getT() << " " << pack.getU() << '\n';
return os;
}
// Main program to demonstrate its use
int main() {
Pack<int,double> p1( 1, 2.3 ), p2( 4, 9.2 ), p3( 5, 3.5 );
C<int, double> c( p1, p2, p3 );
for (auto& p : c.getPacks() )
std::cout << p;
std::cout << '\n';
Pack<float, char> p4( 3.14f, 'a' ), p5( 6.95f, 'b' ),
p6( 2.81f, 'c' ), p7( 8.22f, 'd' );
C<float, char> c2( p4, p5, p6, p7 );
for ( auto& p : c2.getPacks() )
std::cout << p;
return 0;
}
-Output-
1 2.3
4 9.2
5 3.5
3.14 a
6.95 b
2.81 c
8.22 d
-Note- I did not incorporate for any odd
number of parameters. For a more detailed solution with odd cases you can refer to the other answers with SFINAE
or Delegating Constructor
.
Upvotes: 0
Reputation: 4668
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
}
Upvotes: 6
Reputation: 66230
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&&)
{ }
Upvotes: 5