考虑以下代码片段
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 匹配元组内类型的正确构造函数,但我无法弄清楚如何尝试进行匹配,而不管类型的顺序如何或参数。
假设下面的代码代表同一件事:“给我一个带机枪、炮塔和大炮的敌人”
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
应该代表相同的东西,但我如何制作我的编译器明白了吗?
最佳答案
我在你的问题中看到的最大问题是相同的值可以初始化你的元组中的两个不同对象的风险。
举例,给定
Enemy<MachineGun,Turret,Cannon> e2("3",14,'5');
整数(14
)可以同时初始化MachineGun
(一个 int
构造函数)和 Turret
(一个 char
构造函数和从 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;
};
更一般地说,您必须添加所有必要的已删除构造函数以避免所有可能的歧义,并为传递给 Enemy
的每个参数只启用一个构造函数。构造函数。
无论如何,考虑到这种简化,您可以编写几个 SFINAE 替代函数来从列表中选择正确的元素来构造对象
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
而不是 std::enable_if_t<...>
该解决方案也适用于 C++11。
关于c++ - std::tuple,自动构造顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49412609/