Parameter Pack with alternating types
up vote
9
down vote
favorite
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.
c++ templates parameters variadic-templates pack
add a comment |
up vote
9
down vote
favorite
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.
c++ templates parameters variadic-templates pack
1
Does the number of parameters need to be known at compile-time? In any case consider passingstd::pair<A, B>&&
s instead.
– user10605163
Dec 11 at 18:14
During compile time would be the sugar on the top. However, the approach with std::pair had some problems (as I tried it): the expression looks a little weird C c = { A(), { B(), A() } }; and so far I wasn‘t able to hyve something txpesafe with a variable number of params.
– PeWe
Dec 11 at 18:19
Actually I thought this initialization syntax is better, because it clearly shows which two arguments belong together. What do you mean bytypesafe
? You can make sure that the template parameters are as expected withstatic_assert
or SFINAE if you need other overloaded constructors.
– user10605163
Dec 11 at 18:25
Actually, the syntax would be more clear without curly braces in between. I‘m not very familiar with SFINAE, sounds I should learn more about it.
– PeWe
Dec 11 at 18:32
add a comment |
up vote
9
down vote
favorite
up vote
9
down vote
favorite
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.
c++ templates parameters variadic-templates pack
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.
c++ templates parameters variadic-templates pack
c++ templates parameters variadic-templates pack
edited Dec 11 at 18:43
max66
34.1k63762
34.1k63762
asked Dec 11 at 18:01
PeWe
491
491
1
Does the number of parameters need to be known at compile-time? In any case consider passingstd::pair<A, B>&&
s instead.
– user10605163
Dec 11 at 18:14
During compile time would be the sugar on the top. However, the approach with std::pair had some problems (as I tried it): the expression looks a little weird C c = { A(), { B(), A() } }; and so far I wasn‘t able to hyve something txpesafe with a variable number of params.
– PeWe
Dec 11 at 18:19
Actually I thought this initialization syntax is better, because it clearly shows which two arguments belong together. What do you mean bytypesafe
? You can make sure that the template parameters are as expected withstatic_assert
or SFINAE if you need other overloaded constructors.
– user10605163
Dec 11 at 18:25
Actually, the syntax would be more clear without curly braces in between. I‘m not very familiar with SFINAE, sounds I should learn more about it.
– PeWe
Dec 11 at 18:32
add a comment |
1
Does the number of parameters need to be known at compile-time? In any case consider passingstd::pair<A, B>&&
s instead.
– user10605163
Dec 11 at 18:14
During compile time would be the sugar on the top. However, the approach with std::pair had some problems (as I tried it): the expression looks a little weird C c = { A(), { B(), A() } }; and so far I wasn‘t able to hyve something txpesafe with a variable number of params.
– PeWe
Dec 11 at 18:19
Actually I thought this initialization syntax is better, because it clearly shows which two arguments belong together. What do you mean bytypesafe
? You can make sure that the template parameters are as expected withstatic_assert
or SFINAE if you need other overloaded constructors.
– user10605163
Dec 11 at 18:25
Actually, the syntax would be more clear without curly braces in between. I‘m not very familiar with SFINAE, sounds I should learn more about it.
– PeWe
Dec 11 at 18:32
1
1
Does the number of parameters need to be known at compile-time? In any case consider passing
std::pair<A, B>&&
s instead.– user10605163
Dec 11 at 18:14
Does the number of parameters need to be known at compile-time? In any case consider passing
std::pair<A, B>&&
s instead.– user10605163
Dec 11 at 18:14
During compile time would be the sugar on the top. However, the approach with std::pair had some problems (as I tried it): the expression looks a little weird C c = { A(), { B(), A() } }; and so far I wasn‘t able to hyve something txpesafe with a variable number of params.
– PeWe
Dec 11 at 18:19
During compile time would be the sugar on the top. However, the approach with std::pair had some problems (as I tried it): the expression looks a little weird C c = { A(), { B(), A() } }; and so far I wasn‘t able to hyve something txpesafe with a variable number of params.
– PeWe
Dec 11 at 18:19
Actually I thought this initialization syntax is better, because it clearly shows which two arguments belong together. What do you mean by
typesafe
? You can make sure that the template parameters are as expected with static_assert
or SFINAE if you need other overloaded constructors.– user10605163
Dec 11 at 18:25
Actually I thought this initialization syntax is better, because it clearly shows which two arguments belong together. What do you mean by
typesafe
? You can make sure that the template parameters are as expected with static_assert
or SFINAE if you need other overloaded constructors.– user10605163
Dec 11 at 18:25
Actually, the syntax would be more clear without curly braces in between. I‘m not very familiar with SFINAE, sounds I should learn more about it.
– PeWe
Dec 11 at 18:32
Actually, the syntax would be more clear without curly braces in between. I‘m not very familiar with SFINAE, sounds I should learn more about it.
– PeWe
Dec 11 at 18:32
add a comment |
3 Answers
3
active
oldest
votes
up vote
6
down vote
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.
1
I like this answer. One thing you can improve is that you don't needstd::conditional
:template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message ofC c = {}
:could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@palotasb: You could write another ctor that has a!enable_if
and then fails astatic_assert
on the interior
– AndyG
Dec 11 at 20:26
|
show 3 more comments
up vote
5
down vote
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&&)
{ }
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
add a comment |
up vote
0
down vote
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;
}
Working Code
-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
.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53729854%2fparameter-pack-with-alternating-types%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
6
down vote
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.
1
I like this answer. One thing you can improve is that you don't needstd::conditional
:template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message ofC c = {}
:could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@palotasb: You could write another ctor that has a!enable_if
and then fails astatic_assert
on the interior
– AndyG
Dec 11 at 20:26
|
show 3 more comments
up vote
6
down vote
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.
1
I like this answer. One thing you can improve is that you don't needstd::conditional
:template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message ofC c = {}
:could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@palotasb: You could write another ctor that has a!enable_if
and then fails astatic_assert
on the interior
– AndyG
Dec 11 at 20:26
|
show 3 more comments
up vote
6
down vote
up vote
6
down vote
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.
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.
edited Dec 11 at 20:05
answered Dec 11 at 18:25
palotasb
2,33611420
2,33611420
1
I like this answer. One thing you can improve is that you don't needstd::conditional
:template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message ofC c = {}
:could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@palotasb: You could write another ctor that has a!enable_if
and then fails astatic_assert
on the interior
– AndyG
Dec 11 at 20:26
|
show 3 more comments
1
I like this answer. One thing you can improve is that you don't needstd::conditional
:template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message ofC c = {}
:could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@palotasb: You could write another ctor that has a!enable_if
and then fails astatic_assert
on the interior
– AndyG
Dec 11 at 20:26
1
1
I like this answer. One thing you can improve is that you don't need
std::conditional
: template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I like this answer. One thing you can improve is that you don't need
std::conditional
: template <typename X, typename Y, typename... Ts> struct alternates<X, Y, X, Ts...> : alternates<Y, X, Ts...> {};
– AndyG
Dec 11 at 18:46
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
I can't wait for compile-time reflection so that we can write type checks like this without using all these unreadable partial specialization-based techniques.
– Brian
Dec 11 at 18:50
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@AndyG thanks, edited.
– palotasb
Dec 11 at 20:10
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message of
C c = {}
: could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@Brian Might that also result in nicer error messages if I make the effort? I especially didn't like the error message of
C c = {}
: could not convert '<brace-enclosed initializer list>()' from '<brace-enclosed initializer list>' to 'C'
– palotasb
Dec 11 at 20:12
@palotasb: You could write another ctor that has a
!enable_if
and then fails a static_assert
on the interior– AndyG
Dec 11 at 20:26
@palotasb: You could write another ctor that has a
!enable_if
and then fails a static_assert
on the interior– AndyG
Dec 11 at 20:26
|
show 3 more comments
up vote
5
down vote
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&&)
{ }
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
add a comment |
up vote
5
down vote
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&&)
{ }
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
add a comment |
up vote
5
down vote
up vote
5
down vote
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&&)
{ }
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&&)
{ }
answered Dec 11 at 18:38
max66
34.1k63762
34.1k63762
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
add a comment |
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
Well, the hard error can be irritating.
– Deduplicator
Dec 11 at 21:27
add a comment |
up vote
0
down vote
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;
}
Working Code
-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
.
add a comment |
up vote
0
down vote
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;
}
Working Code
-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
.
add a comment |
up vote
0
down vote
up vote
0
down vote
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;
}
Working Code
-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
.
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;
}
Working Code
-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
.
edited Dec 12 at 8:47
palotasb
2,33611420
2,33611420
answered Dec 11 at 19:04
Francis Cugler
4,38411227
4,38411227
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53729854%2fparameter-pack-with-alternating-types%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
Does the number of parameters need to be known at compile-time? In any case consider passing
std::pair<A, B>&&
s instead.– user10605163
Dec 11 at 18:14
During compile time would be the sugar on the top. However, the approach with std::pair had some problems (as I tried it): the expression looks a little weird C c = { A(), { B(), A() } }; and so far I wasn‘t able to hyve something txpesafe with a variable number of params.
– PeWe
Dec 11 at 18:19
Actually I thought this initialization syntax is better, because it clearly shows which two arguments belong together. What do you mean by
typesafe
? You can make sure that the template parameters are as expected withstatic_assert
or SFINAE if you need other overloaded constructors.– user10605163
Dec 11 at 18:25
Actually, the syntax would be more clear without curly braces in between. I‘m not very familiar with SFINAE, sounds I should learn more about it.
– PeWe
Dec 11 at 18:32