How do you static_assert the values in a parameter pack of a variadic template?

血红的双手。 提交于 2019-12-04 17:37:45

问题


I'm creating a variadic template.
Let's say I have something like this:

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... };

    // ...
}

Instances of this class can be instantiated like:

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

I'd like to make sure at compile time using a static_assert that the numbers parameter pack only contains specific numbers. For the sake of this example, let's say I only want to allow 0 or 1.

So I'd like to do something like:

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

But obviously, static_assert doesn't work with a for loop. I'm pretty sure there must be some sort of syntax for this but I haven't been able to figure it out.

I'd prefer to use something that compiles with a C++11 compiler (or perhaps a C++14 compiler, if it isn't doable in C++11).


回答1:


I'll throw in @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, "");

Extract the expression into a constexpr function if it gets complex.




回答2:


Simple C++14 solution:

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.");
};



回答3:


You cannot use a traditional for loop with compile-time values, but there are many ways you can iterate over a compile-time collection. In your case, however, it is not necessary to explicitly loop over every single number: you can use pack expansion to make sure the numbers are only 0 or 1:

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>(), "");
}

If you are looking for an explicit way to iterate over a compile-time collection of elements, I suggest you to look into the following resources:

boost::hana - a modern metaprogramming library that allows compile-time computations using "traditional" imperative syntax.

My CppCon 2015 talk: for_each_argument explained and expanded - using std::tuple and the "type-value encoding" paradigm you can store compile-time numerical values in a tuple and iterate over it at compile time. My talk shows a possible way to iterate in such a way.




回答4:


You can implement your static validation with a recursive template helper, like this. Then when you attempt to compile code with a sequence that contains invalid numbers you will get a compiler error with a static assertion failure as you wanted.

#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;
}



回答5:


  • 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, clang

    static_assert(utility::is_all_true((Numbers == 0 || Numbers == 1)...), "Only ones and zeroes are allowed.");

msvc2015u3 (with workaround for: error C2059: syntax error: '...')

    static constexpr const bool boo = utility::is_all_true((Numbers == 0 || Numbers == 1)...);
    static_assert(boo, "Only ones and zeroes are allowed.");

https://godbolt.org/z/hcS9FY




回答6:


Yet another solution:

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");
};


来源:https://stackoverflow.com/questions/36933176/how-do-you-static-assert-the-values-in-a-parameter-pack-of-a-variadic-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!