【问题标题】:constructor with variadic template arguments of the same type does not compile具有相同类型的可变参数模板参数的构造函数无法编译
【发布时间】:2020-01-22 09:25:05
【问题描述】:

我正在尝试构建一个使用可变参数模板构造函数的类。 模板参数都是同一类型。

我正在使用带有 C++ 17 的 Visual C++ 编译器,但无法构建代码;这段代码实际上使编译器崩溃。

这在 C++17 中可行吗?

 struct Alfa {
     template<int... T>
     Alfa(const T&... all_args) {
         std::tuple<T...> data(all_args...);       
     }    
 };

 Alfa alfa(1,2,3,4);

【问题讨论】:

    标签: c++ c++17 variadic-templates template-argument-deduction


    【解决方案1】:

    模板参数都是相同的类型。 [...] 这在 C++17 中可行吗?

    是的,这是可能的。

    但不是以如此简单的方式并且有一些缺点。

    您可以编写一个接受不同类型参数的构造函数;这很简单

     template <typename ... Ts>
     Alfa (const Ts & ... as) 
      { std::tuple<Ts...> data{as...}; } 
    

    但这允许Ts... 类型不同。

    您可以使用 SFINAE 强制所有类型都相同,如下所示

     template <typename T, typename ... Ts,
               std::enable_if_t<(std::is_same_v<T, Ts> && ...), bool> = true>
     Alfa (T const & a0, Ts const & ... as)
      {
         std::tuple<T, Ts...> data0{a0, as...};       
         std::array           data1{a0, as...};
      }  
    

    因此,只有在第一个 T 之后的所有 Ts... 类型与 T 完全相同时,才会启用您的构造函数

    缺点:适用于

       Alfa alfa{1, 2, 3, 4};
    

    但是用

    给出错误
       Alfa alfa{1l, 2l, 3, 4l};  <-- 3 isn't long
    

    因为3 可以转换为long1llong)但不是long

    因此您可以检查以下Ts... 是否可以转换为T,而不是它们是否相等

     template <typename T, typename ... Ts,
               std::enable_if_t<(std::is_convertible_v<Ts, T> && ...), bool> = true>
     Alfa (T const & a0, Ts const & ... as)
      {
         std::tuple<T, Ts...>             data0{a0, as...};       
         std::array<T, sizeof...(Ts)+1u>  data1{a0, as...};
      }   
    

    但是这样你给T一个更大的重要性给其他类型(如果所有Ts...都可以转换为T,但如果T可以转换为Ts...之一则不行)所以我想更好的解决方案是检查是否存在通用类型

     template <typename ... Ts,
               typename CT = std::common_type_t<Ts...>>
     Alfa (Ts const & ... as)
      {
         std::tuple<Ts...>              data0{as...};       
         std::array<CT, sizeof...(Ts)>  data1{as...};
      }  
    

    【讨论】:

    • 您可以使用std::common_type_t 来简化您的最终示例,例如template&lt;typename... Ts, typename = std::common_type_t&lt;Ts...&gt;&gt;
    • @Kerndog73 - 是的,正是我正在写的内容
    • 哇!我不认为这不是那么简单。我什至不知道 std::common_type_t 并且可以以这种方式使用它。非常感谢两位 cmets。这完美地解决了我的问题。
    • @AbruzzoForteeGentile - 改进了std::common_type的答案
    • 是的,我之前看到了您的回复和 Max Langhof。它确实非常现代,而且更紧凑。它自 C++11 以来就一直存在,但直到今天我才发现它。感谢两者。
    【解决方案2】:

    您必须决定是否需要非类型模板参数。您的代码首先说“T 是整数值”,然后尝试使用 T 就像函数签名和 tuple 模板参数列表中的类型一样。

    有效的选项是:

     struct Alfa {
         template<class ... Ts>
         Alfa(const Ts&... all_args) {
             std::tuple<Ts...> data(all_args...);       
         }    
     };
    
     Alfa alfa(1,2,3,4);
    

    这不编码“所有Ts 必须相同”的约束。没有直接的方法可以做到这一点。您可以使用 static_assert 或 SFINAE(请参阅其他答案)。

     template<int... Is>
     struct Alfa {
         Alfa() {
             std::tuple data(Is...); // Using class template argument deduction.
         }    
     };
    
     Alfa<1,2,3,4> alfa{};
    

    这个模板是类,而不是构造函数,这很可能不是你想要的。它显示了接受编译时整数值的语法(即非类型模板参数包)。但是您不能对非类型模板参数使用推导,因此必须明确指定它们。但这是构造函数的impossible

    妥协可能是:

     struct Alfa {
         template<class ... Ts>
         Alfa(const Ts&... all_args) {
             std::tuple<Ts...> data(all_args...);       
         }    
     };
    
     template<int ... Is>
     auto makeAlfa() { return Alfa(Is...); }
    
     Alfa alfa = makeAlfa<1,2,3,4>();
    

    【讨论】:

    • 确实如此。当我使用模板参数 T 进行编码时,我确实尝试了很多试验和错误来使我的示例工作。我喜欢关于“static_assert”的建议,它非常方便,我也觉得它很时尚。非常感谢您的回复,它也解决了我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多