请考虑以下代码段
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;
};
在上面的构造函数std::forward
中,args unpack与元组内部类型的正确构造函数匹配,但无论类型或参数的顺序如何,我都无法弄清楚如何进行匹配。
假设下面的代码代表同样的事情:&#34;用机枪,炮塔和大炮给我一个敌人&#34;
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');
但是我该如何实现呢? e1
将在args列表与类型列表顺序匹配时进行编译,e2
和e3
应表示相同的内容,但如何让我的编译器理解这一点?
答案 0 :(得分:1)
我在你的问题中看到的最大问题是相同值可以初始化元组中两个不同对象的风险。
例如,给定
Enemy<MachineGun,Turret,Cannon> e2("3",14,'5');
整数(14
)可以初始化MachineGun
(一个int
构造函数)和Turret
(一个char
构造函数以及{{1}的隐式转换转到int
)。
char
的同样问题:可以初始化'5'
(MachineGun
构造函数和从int
到char
的隐式转换)和int
(一个Turret
构造函数)。
我看到解决此问题的唯一方法是避免构造函数冲突添加已删除的构造函数。
因此,您可以为char
char
构造函数
MachineGun
以及struct MachineGun
{
explicit MachineGun (int i)
{ /* something */ }
MachineGun (char) = delete;
};
int
构造函数
Turret
更一般地说,你必须添加所有必需的删除构造函数,以避免所有可能的歧义,并为每个传递给struct Turret
{
explicit Turret (char c)
{ /* something */ }
Turret (int) = delete;
};
构造函数的参数只启用一个构造函数。
无论如何,考虑到这种简化,您可以编写几个SFINAE替代函数来从列表中选择正确的元素来构造对象
Enemy
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)...); }
只是变成
Enemy
以下是一个完整的工作示例
template <typename ... Ts>
struct Enemy
{
private:
std::tuple<Ts...> device_list;
public:
template <typename ... Args>
Enemy (Args ... args)
: device_list { selectArg<Ts>(args...)... }
{ }
};
使用#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');
}
代替typename std::enable_if<...>::type
,解决方案也适用于C ++ 11。