【问题标题】:Implementing variadic type traits实现可变类型特征
【发布时间】:2014-09-01 10:44:00
【问题描述】:

简介

我正在寻找一种模式来将 C++ 类型特征转换为它们的可变参数。解决问题的方法将受到赞赏,而自动化任务的生成式编程模式将是理想的。

示例

采取以下措施:

std::is_same<T, U>::value; 

我想写一个像这样工作的特征:

std::are_same<T1, T2, T3, T4>::value; 

目前的做法

实现are_same 很漂亮straightforward; 寻求一个通用的解决方案,我们可以为实现通用量化的任何可变参数特征提供一个工具:

template<template<class,class> class F, typename...Ts>
struct Univ;

template<template<class, class> class F, typename T, typename U, typename...Ts>
struct Univ<F, T, U, Ts...>
{
    static const int value = F<T, U>::value && Univ<F, U, Ts...>::value;
};

template<template<class, class> class F, typename T>
struct Univ<F, T>
{
    static const int value = 1;
};

所以例如are_same 可以是written as

Univ<is_same,int, int, int>::value

这可能适用于创建 are_classes、are_scalars 等特征时

概括

细微的调整可以让 existential quantification 脱离之前的 sn-p(将 &amp;&amp; 替换为 ||),以便我们以以下方式创建像 exist_same 这样的特征:

Exist<is_same, int, double, float>::value

问题

上一篇关于type traits的封面概括与

  • 主要类型类别
  • 复合类型类别
  • 类型属性
  • 支持的操作

我将如何概括类型特征,如下所示:

    enable_if -> enable_if_any // enable if any clause is true
                 enable_if_all // enalbe if all clauses are true
                 enable_for    // enable only for the type provided

上面的exist_same 示例过于简单。有正确实施的想法吗?

有 type_traits 可以“返回”修改后的类型。有什么建议可以将它们扩展到任意数量类型的实现吗?

是否存在 type_traits,它们 不能扩展到任意数量的类型参数?

【问题讨论】:

  • 对于“返回多种类型”:只能返回一种顶级类型。要返回类型列表,通常使用std::tuple&lt; T1, T2, ... &gt;。
  • 这样的东西让我想要template相当于using——template&lt;template&lt;class,class&gt;class F&gt;using for_all=template&lt;class...Ts&gt;Univ&lt;F,Ts...&gt;;,像for_all&lt;std::is_same&gt;&lt;int,int,int&gt;一样使用,它可以让你轻松地将生成的template&lt;class...&gt;传递给其他元没有丑陋的编程。
  • Univ 是一个非常有用的工具。我想知道您是否可以提供Exist 的实现

标签: c++ templates c++11 template-meta-programming generative-programming


【解决方案1】:

我不完全理解您究竟想要实现什么,但以下帮助程序可能有用,从 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 {};

接下来,您可以检查是否所有或任何布尔值或设置这些:

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

它们可以作为更高级和专业特征的构建块派上用场。例如,您可以像这样使用它们:

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

【讨论】:

  • @0x499602D2 布尔逻辑:bool_or&lt;B1,B2,B3,...&gt; = !bool_and&lt;!B1,!B2,!B3,!...&gt; - 这能回答你的问题吗?否则,请更具体地说明实施的哪一部分给您带来了麻烦。
  • +1 bool_sequence&lt; ( Bs || true )... &gt; 是最方便的sn-p。
  • @Solkar 我会被(Bs,true)... 诱惑。无论如何,我喜欢它具有恒定的递归深度!
  • @Yakk (Bs,true)... 会导致某些编译器出现警告,这就是我使用(Bs||true)... 的原因。
  • @einpoklum 没错。它是一个模板,每组(不同的)参数都会生成不同的类型,而这正是 std::is_same 所需要的。
【解决方案2】:

你也可以使用std::conditional来实现enable_if_all和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 
}

【讨论】:

    【解决方案3】:

    受 Daniel Fray 回答中的绝妙想法的启发,我们甚至可以扩展这些可变参数特征的范围。使用元组,我们可以将特征应用于可变参数类型包的集合,而不是“仅”将可变参数类型包与引用类型进行比较。

    例如,我们将能够查看类型 int, int, int, float 是否与 int, int, int, float 类型相同(确实是!)。

    为此,我们需要以下构造:

    • 元组和产生元组尾部的方法
    • 一种通过向布尔序列附加(或预先附加)布尔值来扩展布尔序列的方法

    TL;DR

    我在这个live demo中编译了几个例子。

    定义可变特征设施

    首先,我们提供一个帮助器来一次扩展一个布尔序列一个值:

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

    现在关于布尔序列的一些逻辑(取自其他答案)

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

    然后,我们定义一个辅助模板来访问元组的尾部:

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

    组合结构

    有了这些积木,我们现在可以定义一个apply_trait 模板来将给定的类型特征应用于多个类型列表:

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

    此模板以自下而上的方式递归计算特征应用程序给出的布尔序列。现在,有了结果 bool 序列,我们可以使用上面定义的帮助器对结果执行逻辑运算。

    接下来,一些助手可以为任何二元(或一元)类型特征重现您的 are_same 示例的逻辑:

    // 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>())...>>;
    

    例如,我们可以重现您为每个二元特征提出的 are_same 设计:

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

    我们还可以将特征逻辑应用于列表。例如,给定两个类型列表,我们可能想要检查第一个列表中的类型是否可以转换为第二个列表中的匹配类型:

    // 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;
    

    【讨论】:

      猜你喜欢
      • 2019-12-08
      • 1970-01-01
      • 1970-01-01
      • 2021-07-02
      • 2018-04-11
      • 1970-01-01
      • 1970-01-01
      • 2022-08-15
      • 1970-01-01
      相关资源
      最近更新 更多