【问题标题】:Check if parameter pack contains a type检查参数包是否包含类型
【发布时间】:2010-01-22 16:04:04
【问题描述】:

我想知道 C++0x 是否提供任何内置功能来检查可变参数模板的参数包是否包含特定类型。今天,如果您使用 boost::mpl::vector 作为可变参数模板的替代品,则可以使用 boost:::mpl::contains 来完成此操作。但是,它具有严重的编译时间开销。我想,C++0x 对 std::is_same 有编译器级别的支持。所以我在想编译器是否也支持in这样的概括。

template <typename... Args, typename What>
struct is_present
{
  enum { value = (What in Args...)? 1 : 0 };
};

【问题讨论】:

    标签: c++ c++11 templates metaprogramming variadic


    【解决方案1】:

    幸运的是,C++ 标准已经发展。使用 C++1z aka C++17,您终于可以轻松地迭代参数包。因此,答案的代码(几乎)与问题中所建议的一样简单:

    template<typename What, typename ... Args>
    struct is_present {
        static constexpr bool value {(std::is_same_v<What, Args> || ...)};
    };
    

    看起来怪异的(std::is_same_v&lt;What, Args&gt; || ...) 在内部由编译器扩展为(std::is_same_v&lt;What, Args[0]&gt; || std::is_same_v&lt;What, Args[1]&gt; || ...),这正是您想要的。它甚至可以正确生成带有空的 Args 参数包的 false

    甚至可以在函数或方法中内联进行整个检查 - 不再需要辅助结构:

    template<typename T, typename ... List>
    void foo(T t, List ... lst)
    {
        if constexpr((std::is_same_v<T, List> || ...)) {
            std::cout << "T is in List" << std::endl;
        } else {
            std::cout << "T is not in List" << std::endl;
        }
    }
    

    注意:这取自another question,它被标记为此问题的重复。由于这是本主题的“规范”问题,因此我在此处添加了重要信息。

    【讨论】:

    • 不错的解决方案。可以简化一点:template&lt;typename What, typename... Args&gt; constexpr inline bool is_present_v = (std::is_same_v&lt;What, Args&gt; || ...);
    【解决方案2】:

    不,您必须对可变参数模板使用(部分)特化来进行编译时计算,如下所示:

    #include <type_traits>
    
    template < typename Tp, typename... List >
    struct contains : std::true_type {};
    
    template < typename Tp, typename Head, typename... Rest >
    struct contains<Tp, Head, Rest...>
    : std::conditional< std::is_same<Tp, Head>::value,
        std::true_type,
        contains<Tp, Rest...>
    >::type {};
    
    template < typename Tp >
    struct contains<Tp> : std::false_type {};
    

    可变参数模板只有一个其他内在操作,即 sizeof 运算符的特殊形式,它计算参数列表的长度,例如:

    template < typename... Types >
    struct typelist_len
    {
       const static size_t value = sizeof...(Types);
    };
    

    boost mpl 从哪里得到“它具有严重的编译时间开销”?我希望你不只是在这里做假设。 Boost mpl 使用惰性模板实例化等技术来尝试减少编译时间,而不是像幼稚的模板元编程那样爆炸。

    【讨论】:

    • 可能是语言/编译器应该支持更大的内在操作集,而不仅仅是 sizeof....(4 个点!)IMO,检查存在与查找大小一样“基本”。根据我编写的这个测试,我觉得 mpl 有性能开销。 dre.vanderbilt.edu/~sutambe/files/mpl_intersection.cpp 我正在使用手工编码的交叉算法以及 MPL 的版本。 g++ 4.4 需要相同的时间来编译两者。可变参数模板版本编译速度快 10 倍。顺便说一句,你能推荐我读一下 mpl 的惰性模板实例化技术吗?
    • 我在 C++ Template Metaprogramming 一书中找到了一些惰性求值的好例子。这不是很明显吗?!无论如何,谢谢。
    • 是的,你所要做的就是在将结果提供给另一个 boost 元函数之前,尽量避免元函数的模板实例化(通过暴露嵌套类型别名类型)。Boost 元函数有旨在评估元函数在最后一刻需要嵌套类型别名。您还应该尝试避免裸值并使用元数据类型包装器(如 mpl::bool_),因为它们也被设计为懒惰地工作. 有时 boost mpl 提供了两种形式的元函数,尽量使用促进惰性实例化的一种。
    • 我觉得这是一个更平易近人的答案,因为它很简单!
    • 我更喜欢使用专业技术来匹配它:template&lt;typename M, typename ...L&gt; struct C : std::false_type {}; template&lt;typename M, typename ...L&gt; struct C&lt;M, M, L...&gt; : std::true_type {}; template&lt;typename M, typename L1, typename ...L&gt; struct C&lt;M, L1, L...&gt; : C&lt;M, L...&gt; {};
    【解决方案3】:

    如果您想避免手动类型递归,在我看来,std::common_type 是 STL 中唯一一个作为可变参数模板的实用程序,因此也是唯一一个可能封装递归的实用程序。


    解决方案 1

    std::common_type 在一组类型中查找派生最少的类型。如果我们用类型来识别数字,特别是具有较少派生类型的高数字,它会在集合中找到最大的数字。然后,我们必须将密钥类型的相等性映射到派生级别。

    using namespace std;
    
    struct base_one { enum { value = 1 }; };
    struct derived_zero : base_one { enum { value = 0 }; };
    
    template< typename A, typename B >
    struct type_equal {
     typedef derived_zero type;
    };
    
    template< typename A >
    struct type_equal< A, A > {
     typedef base_one type;
    };
    
    template< typename Key, typename ... Types >
    struct pack_any {
     enum { value =
         common_type< typename type_equal< Key, Types >::type ... >::type::value };
    };
    


    解决方案 2

    我们可以多破解common_type。标准说

    如果满足以下条件,程序可能会专门化此特征 至少有一个模板参数 专业化是用户定义的类型。

    并准确描述其中的内容:递归部分特化情况、应用二元运算符的情况和终结情况。本质上,它是一个通用的fold 函数,你可以添加任何你喜欢的二进制操作。这里我使用了加法,因为它比 OR 提供的信息更多。请注意,is_same 返回一个 integral_constant

    template< typename Addend >
    struct type_sum { // need to define a dummy type to turn common_type into a sum
        typedef Addend type;
    };
    
    namespace std { // allowed to specialize this particular template
    template< typename LHS, typename RHS >
    struct common_type< type_sum< LHS >, type_sum< RHS > > {
        typedef type_sum< integral_constant< int,
         LHS::type::value + RHS::type::value > > type; // <= addition here
    };
    }
    
    template< typename Key, typename ... Types >
    struct pack_count : integral_constant< int,
     common_type< type_sum< is_same< Key, Types > > ... >::type::type::value > {};
    

    【讨论】:

    • 这是一个脑筋急转弯。但我喜欢它!假设对 common_type 特征有很好的理解。我不得不在 C++0x 公开草案中挖掘它。将其与通过 type_equal 隐式转换为 base_one 相结合是聪明的。 C++ 有太多这些巧妙的技巧。可以使用 std::is_same 和逻辑或以某种方式完成更直观的事情吗?
    • 这有点好,但仍然必须内化令人麻木的细节。
    • @Alexandre:谢谢。实际上我一直在做一些函数式编程,只是想知道这一点!我不记得了,因为 Python 称它为 reduce 而 C++ 称它为 accumulate
    • @Potatoswatter:zip 接受两个列表并返回一个对列表(因此是“zip”名称)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    相关资源
    最近更新 更多