【问题标题】:Initialise `std::array` with parameter pack arguments使用参数包参数初始化 `std::array`
【发布时间】:2019-06-24 18:54:58
【问题描述】:

结构体HasArray 带有模板参数typename T 和size_t N

template<typename T, size_t N>
struct HasArray {
  // enable_if sizeof...(T) equals N
  template<typename ... Types, typename std::enable_if<sizeof...(Types) == N, int>::type = 0>
  explicit HasArray(Types ... s) : arr { s... } {}
 protected:
  std::array<T, N> arr;
};

我想用构造函数的参数包参数初始化成员数组arr

HasArray<uint32_t, 2> foo(7, 13);

但这会在 Clang 中产生 c++11-narrowing 警告。

error: non-constant-expression cannot be narrowed from type 'int' to 'std::__1::array<unsigned int, 2>::value_type' (aka 'unsigned int') in initializer list [-Wc++11-narrowing]

我看不到将所有Types 类型的s 转换为T 类型的方法。有吗?

编辑感谢所有回答。我最终在打包参数和 SFINAE 上使用 static_cast&lt;&gt; 在不可转换时使用:

template<typename T, size_t N>
struct HasArray {
  // Use `static_cast` to construct `arr` with `s`
  // Add `enable_if` all `Types` are `is_convertible`
  template<typename ... Types, 
    typename std::enable_if<sizeof...(Types) == N, int>::type = 0,
    typename std::enable_if<(std::is_convertible<Types, T>::value && ...), int>::type = 0>
  explicit HasArray(Types ... s) : arr { static_cast<T>(s)... } {}
 protected:
  std::array<T, N> arr;
};

【问题讨论】:

  • 我想是sizeof...(Types) == N;无论如何:C++11、C++14 还是 C++17?
  • 您想使用任何可转换为T 类型的值还是仅使用T 类型的值来初始化arr
  • [at]max66,是的,这是摘录中的错字,抱歉。 @丹姆。我想用任何可转换的值构造HasArray

标签: c++ variadic-templates template-meta-programming sfinae template-argument-deduction


【解决方案1】:

如果您想从任何可转换为T 的值构造std::array,那么最简单的解决方案就是在构造函数中添加static_cast&lt;T&gt;(...)

template<typename T, size_t N>
struct HasArray {
  template<typename ... Types,
           typename std::enable_if<sizeof...(Types) == N, int>::type = 0>
  explicit HasArray(Types ... s) : arr {static_cast<T>(s)... } {}
protected:
  std::array<T, N> arr;
};

https://godbolt.org/z/TEoZOG

如果无法进行这种转换,也可以“SFINAE out”构造函数,但我认为在当前简单的情况下默认错误消息会更好,您可以在构造函数正文中添加带有更好消息的 static_asserts。

【讨论】:

    【解决方案2】:

    你可以使用中间类:

    template <std::size_t, typename T>
    using always_t = T;
    
    template <typename T, typename Seq>
    struct HasArrayImpl;
    
    template <typename T, std::size_t...Is>
    struct HasArrayImpl<T, std::index_sequence<Is...>>
    {
        explicit HasArrayImpl(always_t<Is, T> ... v) : arr { v... } {}
    protected:
        std::array<T, sizeof...(Is)> arr;
    };
    
    template <typename T, std::size_t N>
    using HasArray = HasArrayImpl<T, std::make_index_sequence<N>>;
    

    否则,您可以将 SFINAE 扩展为可转换类型并显式转换值

    template<typename T, size_t N>
    struct HasArray {
        // enable_if sizeof...(T) equals N
        template <typename ... Types,
                 std::enable_if_t<(sizeof...(Types) == N)
                                       && (std::is_convertible<Types, T>::value && ...),
                                  int>::type = 0>
        explicit HasArray(Types ... s) : arr{ static_cast<T>(s)... } {}
     protected:
        std::array<T, N> arr;
    };
    

    【讨论】:

    • 嗯,我不确定我是否喜欢将完整索引序列作为模板参数的实际类型。当然,它是有效的 C++,但你会弄乱很多工具、错误消息、二进制大小(想想void foo(HasArray&lt;int, 100&gt;&amp; x) 的错误名称)......这个答案在技术上是正确的,但在实践中并不建议这样做。
    • 还要注意std::is_convertiblestatic_cast 更严格。前者只考虑implicit conversions,而后者可以考虑a good deal more
    【解决方案3】:

    我认为没有办法强制所有类型的类型都必须是 T 类型。有吗?

    我不明白,从您的问题来看,如果您希望 Types 是可推导出的模板类型,并且所有这些都完全推导出为 T,或者如果您想要一个 no-接收 T 类型的精确 N 值的模板构造函数。

    第一种情况很简单(如果你可以使用 C++17 模板折叠;否则稍微复杂一些)因为你可以使用std::is_same

    template <typename ... Types,
              typename std::enable_if<(sizeof...(Types) == N) 
                  && (... && std::is_same<Types, T>::value), int>::type = 0>
    explicit HasArray(Types ... s) : arr {{ s... }}
     { }
    

    对于第二种情况,我提出了 Jarod42 解决方案的变体,它使用 HasArray 的特化而不是中间类(编辑:添加了对 Jarod42 本身的改进;谢谢!):

    template<typename T, std::size_t N, typename = std::make_index_sequence<N>>
    struct HasArray;
    
    template<typename T, std::size_t N, std::size_t ... Is>
    struct HasArray<T, N, std::index_sequence<Is...>>
     {
       static_assert( sizeof...(Is) == N , "wrong sequence size" );
    
       protected:
          std::array<T, N> arr;
    
       public:
          explicit HasArray(getType<T, Is> ... s) : arr {{ s... }}
           { }
     };
    

    getType 在哪里

    template <typename T, std::size_t>
    using getType = T;
    

    在第一种情况下,

    HasArray<std::uint32_t, 2> foo(7, 13);
    

    给出编译错误,因为713 被推断为int 而不是std::uin32_t

    在第二种情况下它会编译,因为HasArray 有一个构造函数

    HasArray(std::uint32_t, std::uint32_t)
    

    并且ints 被转换为std::uint32_t

    以下是第二种情况的完整编译 C++14 示例

    #include <array>
    #include <cstdint>
    #include <type_traits>
    
    template <typename T, std::size_t>
    using getType = T;
    
    template<typename T, std::size_t N, typename = std::make_index_sequence<N>>
    struct HasArray;
    
    template<typename T, std::size_t N, std::size_t ... Is>
    struct HasArray<T, N, std::index_sequence<Is...>>
     {
       static_assert( sizeof...(Is) == N , "wrong sequence size" );
    
       protected:
          std::array<T, N> arr;
    
       public:
          explicit HasArray(getType<T, Is> ... s) : arr {{ s... }}
           { }
     };
    
    int main ()
     {
       HasArray<std::uint32_t, 2> foo(7, 13);
     }
    

    【讨论】:

    • 你可能想要static_assert(N == sizeof...(Is)),否则HasArray&lt;int, 42, std::index_sequence&lt;&gt;&gt; 将是有效类型:-/
    • @Jarod42 - 我好像忘记了什么:HasArray 可以被劫持。谢谢。
    猜你喜欢
    • 2020-01-27
    • 1970-01-01
    • 2020-08-08
    • 1970-01-01
    • 2019-04-28
    • 2015-01-02
    • 1970-01-01
    • 2014-03-30
    • 1970-01-01
    相关资源
    最近更新 更多