Consider the following code snippet
struct MachineGun {
explicit MachineGun (int a);
};
struct Turret {
explicit Turret (char b);
};
struct Cannon {
explicit Cannon (std::string c);
};
template<typename... Ts>
struct Enemy {
template<typename...Args>
Enemy(Args&&... args)
: device_list {std::forward<Args>(args)...}
{
}
private:
std::tuple<Ts...> device_list;
};
In the above constructor std::forward
with the args unpack matches the correct constructor of the type inside the tuple but I cannot figure out how to try to make this match regardless of order of the types or parameters.
Hypothetically the code below represent the same thing: "Give me an enemy with a machinegun, turret and cannon"
Enemy<MachineGun,Turret,Cannon> e1(1,'1',std::string("1"));
Enemy<MachineGun,Turret,Cannon> e2("3",14,'5');
Enemy<Turret,MachineGun,Cannon> e3(std::string("14"), 5, '33');
But how do I actually enable this? e1
will compile as the args list matches the type list order, both e2
and e3
should represent the same thing but how do I make my compiler understand this?
The biggest problem that I see in your question is the risk that the same value can initialize two different object in your tuple.
By example, given
Enemy<MachineGun,Turret,Cannon> e2("3",14,'5');
the integer ( 14
) can initialize both MachineGun
(a int
constructor) and Turret
(a char
constructor and implicit conversion from int
to char
).
Same problem for '5'
: can initialize both MachineGun
(a int
constructor and implicit conversion from char
to int
) and Turret
(a char
constructor).
The only way I see to solve this problem is to avoid constructor collisions adding deleted constructors.
So you can add a deleted char
constructor for MachineGun
struct MachineGun
{
explicit MachineGun (int i)
{ /* something */ }
MachineGun (char) = delete;
};
and a deleted int
constructor for Turret
struct Turret
{
explicit Turret (char c)
{ /* something */ }
Turret (int) = delete;
};
More generally speaking, you have to add all deleted constructor necessary to avoid all possible ambiguities and to enable only one constructor for every argument you pass to Enemy
constructor.
Anyway, given this simplification, you can write a couple of SFINAE alternative functions to select the right element, from a list, to construct an object
template <typename T, typename A0, typename ... As>
std::enable_if_t<true == std::is_constructible<T, A0>::value, T>
selectArg (A0 && a0, As ...)
{ return T{ std::forward<A0>(a0) }; }
template <typename T, typename A0, typename ... As>
std::enable_if_t<false == std::is_constructible<T, A0>::value, T>
selectArg (A0, As && ... as)
{ return selectArg<T>(std::forward<As>(as)...); }
And Enemy
simply become
template <typename ... Ts>
struct Enemy
{
private:
std::tuple<Ts...> device_list;
public:
template <typename ... Args>
Enemy (Args ... args)
: device_list { selectArg<Ts>(args...)... }
{ }
};
The following is a full working example
#include <tuple>
#include <iostream>
#include <type_traits>
struct MachineGun
{
explicit MachineGun (int i)
{ std::cout << "- MachineGun: " << i << std::endl; }
MachineGun (char) = delete;
};
struct Turret
{
explicit Turret (char c)
{ std::cout << "- Turret: " << c << std::endl; }
Turret (int) = delete;
};
struct Cannon
{
explicit Cannon (std::string const & s)
{ std::cout << "- Cannon: " << s << std::endl; }
};
template <typename T, typename A0, typename ... As>
std::enable_if_t<true == std::is_constructible<T, A0>::value, T>
selectArg (A0 && a0, As ...)
{ return T{ std::forward<A0>(a0) }; }
template <typename T, typename A0, typename ... As>
std::enable_if_t<false == std::is_constructible<T, A0>::value, T>
selectArg (A0, As && ... as)
{ return selectArg<T>(std::forward<As>(as)...); }
template <typename ... Ts>
struct Enemy
{
private:
std::tuple<Ts...> device_list;
public:
template <typename ... Args>
Enemy (Args ... args)
: device_list { selectArg<Ts>(args...)... }
{ }
};
int main()
{
Enemy<MachineGun,Turret,Cannon> e1(1,'2',std::string("3"));
Enemy<MachineGun,Turret,Cannon> e2("3",14,'5');
Enemy<Turret,MachineGun,Cannon> e3(std::string("14"), 5, '3');
}
Using typename std::enable_if<...>::type
instead of std::enable_if_t<...>
the solution works also with C++11.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.