std :: tuple,自动构造函数顺序

时间:2018-03-21 17:10:02

标签: c++ c++11 templates c++14 variadic-templates

请考虑以下代码段

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列表与类型列表顺序匹配时进行编译,e2e3应表示相同的内容,但如何让我的编译器理解这一点?

1 个答案:

答案 0 :(得分:1)

我在你的问题中看到的最大问题是相同值可以初始化元组中两个不同对象的风险。

例如,给定

Enemy<MachineGun,Turret,Cannon> e2("3",14,'5');

整数(14)可以初始化MachineGun(一个int构造函数)和Turret(一个char构造函数以及{{1}的隐式转换转到int)。

char的同样问题:可以初始化'5'MachineGun构造函数和从intchar的隐式转换)和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。