Implementing variadic type traits

自古美人都是妖i 提交于 2019-11-28 06:54:45

I don't fully understand what exactly you'd like to achieve, but the following helpers might be useful, starting with bool_sequence:

#include <type_traits>

// Note: std::integer_sequence is C++14,
// but it's easy to use your own version (even stripped down)
// for the following purpose:
template< bool... Bs >
using bool_sequence = std::integer_sequence< bool, Bs... >;

// Alternatively, not using C++14:
template< bool... > struct bool_sequence {};

next, you can check if all or any boolean value or set with these:

template< bool... Bs >
using bool_and = std::is_same< bool_sequence< Bs... >,
                               bool_sequence< ( Bs || true )... > >;

template< bool... Bs >
using bool_or = std::integral_constant< bool, !bool_and< !Bs... >::value >;

they come in handy as building blocks for more advanced and specialized traits. For example, you could use them like this:

typename< typename R, bool... Bs > // note: R first, no default :(
using enable_if_any = std::enable_if< bool_or< Bs... >::value, R >;

typename< typename R, bool... Bs > // note: R first, no default :(
using enable_if_all = std::enable_if< bool_and< Bs... >::value, R >;

typename< typename T, typename... Ts >
using are_same = bool_and< std::is_same< T, Ts >::value... >;

You can also use std::conditional in order to achieve enable_if_all and enable_if_any:

#include <type_traits>
#include <iostream>
#include <initializer_list>
#include <string>

namespace detail
{
template <typename... Conds>
struct and_ : std::true_type {};

template <typename... Conds>
struct or_ : std::false_type {};

template <typename Cond, typename... Conds>
struct and_<Cond, Conds...>
    : std::conditional<Cond::value, detail::and_<Conds...>, std::false_type>::type {};

template <typename Cond, typename... Conds>
struct or_<Cond, Conds...>
    : std::conditional<Cond::value, std::true_type, detail::and_<Conds...>>::type {};
}

template <typename... T>
using are_all_pod = detail::and_<std::is_pod<T>...>;

template <typename... T>
using any_is_pod = detail::or_<std::is_pod<T>...>;

template <typename... Args, typename = typename std::enable_if<are_all_pod<Args...>::value>::type>
void f(Args... args)
{
  (void)std::initializer_list<int>{(std::cout << args << '\n' , 0)...};
}

template <typename... Args, typename = typename std::enable_if<any_is_pod<Args...>::value>::type>
void g(Args... args)
{
  (void)std::initializer_list<int>{(std::cout << args << '\n' , 0)...};
}

int main()
{
  std::string s = "hello";  // non pod
  //f(1, 1.2, s); // this will fail because not all types are pod
  g(1, 1.2, s);   // this compiles because there is at least one pod in argument pack 
}

Inspired by the excellent idea in Daniel Fray's answer, we can even extend the scope of these variadic traits. Using tuples, we can apply traits on collections of variadic type packs instead of "only" comparing a variadic pack of types to a reference type.

For example, we will be able to see if the types int, int, int, float are the same types as int, int, int, float (indeed they are!).

To do so, we need the following constructs:

  • Tuples and a way to produce the tail of a tuple
  • A way to extend bool sequences by appending (or prepending) boolean values to it

TL;DR

I compiled a few examples in this live demo.

Defining the variadic trait facilities

First we provide a helper to extend a bool sequence one value at a time:

template <bool ... Bs>
struct bool_sequence {};

template <bool b, typename T>
struct prepend_bool_seq;

template <bool b, bool ... bs>
struct prepend_bool_seq<b, bool_sequence<bs...>> {
    typedef bool_sequence<b, bs...> type;
};

Now some logic on bool sequences (taken from other answers)

template <typename T>
struct all_of;

template <bool ... Bs>
struct all_of<bool_sequence<Bs...>> :
    public std::is_same<bool_sequence<true, Bs...>, bool_sequence<Bs..., true>> {};

template <typename T>
struct any_of;

template <bool ... Bs>
struct any_of<bool_sequence<Bs...>> :
    public std::integral_constant<bool, !all_of<bool_sequence<!Bs...>>::value> {};

Then, we define an helper template to access a tuple's tail:

namespace details {

// Sentinel type to detect empty tuple tails
struct null_type {};

template <typename T>
struct tuple_tail;

template <typename T>
struct tuple_tail<std::tuple<T>> {
    typedef null_type type;
};

template <typename T, typename ... Ts>
struct tuple_tail<std::tuple<T, Ts...>> {
    typedef std::tuple<Ts...> type;
};

}

Combining the constructs

With these bricks, we can now define an apply_trait template to apply a given type trait on several type lists:

namespace details {

template <template <typename...> class Trait, typename ... Tuples>
struct apply_trait {
    static constexpr bool atomic_value =
                     Trait<typename std::tuple_element<0u, Tuples>::type...>::value;
    typedef typename prepend_bool_seq<atomic_value,
                     typename apply_trait<Trait,
                     typename tuple_tail<Tuples>::type...>::type>::type type;
};

template <template <typename...> class Trait, typename ... Tuples>
struct apply_trait<Trait, null_type, Tuples...> {
    typedef bool_sequence<> type;
};

}

This template recursively computes the bool sequence given by the trait application in a bottom-up fashion. Now, provided with the resulting bool sequence, we can perform logical operations on the result with the helpers defined above.

Next, some helpers can reproduce the logic of your are_same example for any binary (or unary) type trait:

// Helper templates for common type traits (unary and binary)
template <template <typename> class UnaryTrait, typename ... Ts>
using apply_unary_trait = details::apply_trait<UnaryTrait, std::tuple<Ts...>>;

template <template <typename, typename> class BinaryTrait, typename Ref, typename ... Ts>
using apply_binary_trait = details::apply_trait<BinaryTrait,
                                                std::tuple<decltype(std::declval<Ts>(), std::declval<Ref>())...>,
                                                std::tuple<Ts...>>;

template <template <typename, typename> class BinaryTrait, typename Ref, typename ... Ts>
using apply_binary_trait_ref_last = details::apply_trait<BinaryTrait,
                                             std::tuple<Ts...>,
                                             std::tuple<decltype(std::declval<Ts>(), std::declval<Ref>())...>>;

For example, we can reproduce the are_same design you brought up for every binary trait:

template <typename Ref, typename ... Ts>
using are_same = all_of<typename apply_binary_trait<std::is_same, Ref, Ts...>::type>;

We can also apply the traits logic on lists. For example, given two lists of types, we may want to check if a type in the first list is convertible to its matching type in the second list:

// int is convertible to long and char const* is convertible to std::string
std::cout << all_of<details::apply_trait<std::is_convertible,
                                         std::tuple<int, char const*>,
                                         std::tuple<long, std::string>::type>::value;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!