【问题标题】:How do you static_assert the values in a parameter pack of a variadic template?你如何静态断言可变参数模板的参数包中的值?
【发布时间】:2016-04-29 08:18:49
【问题描述】:

我正在创建一个可变参数模板。
假设我有这样的事情:

template<typename T, T ... Numbers>
class Sequence final {

    // Unpack parameter pack into a constexpr array
    constexpr static T count = sizeof...(Numbers);        
    constexpr static T numbers[count] = { Numbers... };

    // ...
}

这个类的实例可以这样实例化:

Sequence<uint32_t, 1, 2, 3, 42, 25> seq;

我想在编译时使用static_assert 确保numbers 参数包仅包含特定数字。为了这个例子,假设我只想允许01

所以我想做这样的事情:

for (size_t i = 0; i < count; i++) {
    static_assert(numbers[i] == 1 || numbers[i] == 0, "Only ones and zeroes are allowed.");
}

但显然,static_assert 不适用于 for 循环。我很确定这一定有某种语法,但我无法弄清楚。

我更喜欢使用可以通过 C++11 编译器(或者可能是 C++14 编译器,如果在 C++11 中不可行的话)编译的东西。

【问题讨论】:

  • 在 C++17 中:static_assert(((Numbers == 0 || Numbers == 1) &amp;&amp; ... &amp;&amp; true)); (Demo)
  • 我认为 BOOST_PP_COMMA_IFBOOST_PP_SEQ_FOR_EACH_I 可以在使用 boost 的情况下解决问题。
  • @KerrekSB &amp;&amp; true 也是可选的。

标签: c++ c++11 variadic-templates static-assert


【解决方案1】:

我会加入@Columbo's bool_pack trick

template<bool...> struct bool_pack;
template<bool... bs> 
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;

static_assert(all_true<(Numbers == 0 || Numbers == 1)...>::value, "");

如果表达式变得复杂,请将其提取到 constexpr 函数中。

【讨论】:

    【解决方案2】:

    简单的 C++14 解决方案:

    template <typename T, T ... Numbers>
    class Sequence final {
      static constexpr bool is_all_zero_or_one(std::initializer_list<T> list) {
        for (auto elem : list) {
          if (elem != 0 && elem != 1) return false;
        }
        return true;
      }
    
      static_assert(is_all_zero_or_one({Numbers...}),
                    "Only zeroes and ones are allowed.");
    };
    

    【讨论】:

    • @Venemo:不可以。C++11 constexpr 函数不允许有for 循环。
    【解决方案3】:

    基于@T.C.'s answer的单行c++17解决方案。

    #include <type_traits>
    static_assert(std::conjunction<std::bool_constant<(Numbers == 0 || Numbers == 1)>...>::value, "");
    

    实际上这可以用 c++11 完成,因为 std::conjunction 和 std::bool_constant 都是纯粹的库功能并且不需要任何 核心语言功能超越 c++11。

    【讨论】:

    • 或者,更简单地说:static_assert(((Numbers == 0 || Numbers == 1) &amp;&amp; ...), "all numbers must be 0 or 1");(C++17 因为它需要折叠表达式)
    【解决方案4】:

    您不能将传统的 for 循环与编译时值一起使用,但您可以通过多种方式迭代编译时集合。但是,在您的情况下,不必显式循环每个数字:您可以使用包扩展来确保数字仅为 01

    coliru example

    #include <type_traits>
    
    // We define a `conjunction<...>` helper that will evaluate to
    // a true boolean `std::integral_constant` if all passed types evaluate
    // to true.
    
    template <typename...>
    struct conjunction : std::true_type
    {
    };
    
    template <typename T>
    struct conjunction<T> : T
    {
    };
    
    template <typename T, typename... Ts>
    struct conjunction<T, Ts...>
        : std::conditional_t<T::value != false, conjunction<Ts...>, T>
    {
    };
    
    // Define `constexpr` predicates:
    
    template <int T>
    constexpr bool is_zero_or_one()
    {
        return T == 0 || T == 1;
    }
    
    template <int... Ts>
    constexpr bool all_are_zero_or_one()
    {
        // Using variadic pack expansion and `conjunction` we can
        // simulate an `and` left fold over the parameter pack:
        return conjunction<
            std::integral_constant<bool, is_zero_or_one<Ts>()>...
        >{};
    }
    
    int main()
    {
        static_assert(all_are_zero_or_one<0, 1, 0, 1, 0, 0>(), "");
        static_assert(!all_are_zero_or_one<2, 1, 0, 1, 0, 0>(), "");
    }
    

    如果您正在寻找一种显式方法来迭代编译时元素集合,我建议您查看以下资源:

    boost::hana - 一个现代元编程库,允许使用“传统”命令式语法进行编译时计算。

    My CppCon 2015 talk: for_each_argument explained and expanded - 使用std::tuple 和“类型-值编码”范例,您可以将编译时数值存储在一个元组中,并在编译时对其进行迭代。我的演讲展示了一种以这种方式进行迭代的可能方式。

    【讨论】:

    • 谢谢维托里奥!不,我不想遍历元素,我只是在寻找一种方法来确保在编译时所有数字都是 01
    【解决方案5】:

    您可以使用递归模板助手来实现静态验证,如下所示。然后,当您尝试使用包含无效数字的序列编译代码时,您将得到一个编译器错误,如您所愿,静态断言失败。

    #include <iostream>
    
    template<typename T, T... Numbers>
    struct ValidateSequence;
    
    template<typename T>
    struct ValidateSequence<T>{};
    
    template<typename T, T Number, T... Numbers>
    struct ValidateSequence<T, Number, Numbers...>
    {
        static_assert(Number == 0 || Number == 1, "Invalid Number");
    
        ValidateSequence<T, Numbers...> rest;
    };
    
    template<typename T, T... Numbers>
    class Sequence
    {
    public:
        constexpr static unsigned count = sizeof...(Numbers);
        constexpr static T numbers[] = {Numbers...};
    
        ValidateSequence<T, Numbers...> validate;
    };
    
    int main()
    {
        Sequence <int, 1, 2, 1, 2> sec;
    
        std::cout << sec.count << std::endl;
        return 0;
    }
    

    【讨论】:

    • 我喜欢这个,但是稍微调整一下,你会得到一个更好的错误消息,明确地告诉你哪个参数验证失败,比如:static_assert failed "Invalid Number" [...] in instantiation of template class 'Validate&lt;int, 2&gt;' requested hereexample
    【解决方案6】:
    • C++11

    • msvc2015u3,gcc5.4,clang3.8

        #include <cstdint>
        #include <algorithm>
      
        namespace utility {
      
            template <typename T0>
            inline constexpr bool is_all_true(T0 && v0)
            {
                return std::forward<T0>(v0) ? true : false;
            }
      
            template <typename T0, typename... Args>
            inline constexpr bool is_all_true(T0 && v0, Args &&... args)
            {
                return (std::forward<T0>(v0) ? true : false) && is_all_true(std::forward<Args>(args)...);
            }
      
            template <typename T0>
            inline constexpr bool is_all_false(T0 && v0)
            {
                return std::forward<T0>(v0) ? false : true;
            }
      
            template <typename T0, typename... Args>
            inline constexpr bool is_all_false(T0 && v0, Args &&... args)
            {
                return (std::forward<T0>(v0) ? false : true) && is_all_false(std::forward<Args>(args)...);
            }
      
            template <typename T0>
            inline constexpr bool is_any_true(T0 && v0)
            {
                return std::forward<T0>(v0) ? true : false;
            }
      
            template <typename T0, typename... Args>
            inline constexpr bool is_any_true(T0 && v0, Args &&... args)
            {
                return (std::forward<T0>(v0) ? true : false) || is_any_true(std::forward<Args>(args)...);
            }
      
            template <typename T0>
            inline constexpr bool is_any_false(T0 && v0)
            {
                return std::forward<T0>(v0) ? false : true;
            }
      
            template <typename T0, typename... Args>
            inline constexpr bool is_any_false(T0 && v0, Args &&... args)
            {
                return (std::forward<T0>(v0) ? false : true) || is_any_false(std::forward<Args>(args)...);
            }
      
        }
      

    '

    #gcc, 铿锵声 static_assert(utility::is_all_true((Numbers == 0 || Numbers == 1)...), "只允许 1 和 0。");

    #msvc2015u3(解决方法:error C2059: syntax error: '...') static constexpr const bool boo = utility::is_all_true((Numbers == 0 || Numbers == 1)...); static_assert(boo, "只允许 1 和 0。");

    https://godbolt.org/z/hcS9FY

    作为tacklelib 库的一部分实现:

    https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/utility/type_identity.hpp
    https://github.com/andry81/tacklelib/tree/trunk/include/tacklelib/utility/type_identity.hpp

    【讨论】:

      【解决方案7】:

      另一个解决方案:

      template<typename T>
      constexpr bool IsOneOrZero(T&& t) {
          return t == 0 || t == 1;
      }
      
      template<typename T, typename... Args>
      constexpr bool IsOneOrZero(T&& first, Args&&... args) {
          return IsOneOrZero(std::forward<T>(first)) && IsOneOrZero(std::forward<Args>(args)...);
      }
      
      template<typename T, T First, T... Numbers>
      class Sequence final {
      
          // Unpack parameter pack into a constexpr array
          constexpr static T count = sizeof...(Numbers);        
          constexpr static T numbers[count] = { Numbers... };
      
          static_assert(IsOneOrZero(First, Numbers...), "ERROR");
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-05
        • 1970-01-01
        相关资源
        最近更新 更多