检查参数包是否包含某种类型

14
我想知道C++0x是否提供了内置功能来检查变长模板的参数包中是否包含特定类型。今天,如果您使用boost::mpl::vector作为真正变长模板的替代品,则可以使用boost::mpl::contains来完成此操作。然而,这会在编译时产生严重的开销。我认为,C++0x具有std::is_same的编译器级支持。所以我在想,像下面这样的泛化是否也被编译器支持。
template <typename... Args, typename What>
struct is_present
{
  enum { value = (What in Args...)? 1 : 0 };
};
4个回答

28

幸运的是,C++标准已经发展。随着C++1z(又称C++17)的到来,您可以轻松地对参数包进行迭代。因此,答案的代码(几乎)与问题中建议的一样简单:

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

The weird-looking (std::is_same_v<What, Args> || ...) is internally expanded by the compiler to (std::is_same_v<What, Args[0]> || std::is_same_v<What, Args[1]> || ...), which is exactly what you need. It correctly returns false even with an empty Args parameter pack. It's also possible to perform the entire check inline in a function or method without requiring any helper structures.
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;
    }
}

注意:这段内容来自于另一个问题,该问题被标记为与本问题重复。由于这是该主题的“规范”问题,因此我在此处添加了这个重要信息。

2
不错的解决方案。它可以简化一下:template<typename What, typename... Args> constexpr inline bool is_present_v = (std::is_same_v<What, Args> || ...); - Vladislav

7
不,您需要使用可变参数模板的(部分)特化来进行像这样的编译时计算:
#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 更多的内在操作。我认为,检查存在与找到大小一样“基础”。通过我编写的测试,我感觉 mpl 有性能开销。链接是:http://www.dre.vanderbilt.edu/~sutambe/files/mpl_intersection.cpp 。我使用手工编码的 Intersection 算法和 MPL 的版本。g++ 4.4 编译它们的时间相同。可变参模板版本编译速度快了10倍。顺便问一下,您可以建议我一些关于 mpl 惰性模板实例化技术的阅读材料吗? - Sumant
我在C++模板元编程书中找到了一些很好的惰性求值示例。这不是显而易见的吗?无论如何,还是谢谢。 - Sumant
是的,你所要做的就是在将结果提供给另一个boost元函数之前,尝试避免元函数模板实例化(通过公开嵌套类型别名type)。Boost元函数被设计为在需要嵌套类型别名时才评估元函数。你还应该尝试避免裸值,并使用元数据类型包装器(如mpl::bool_),因为它们也被设计为懒惰地工作。有时,boost mpl提供了一个元函数的两种形式,请尝试使用促进懒惰实例化的那个。 - snk_kid
我觉得这个答案更易于理解,因为它很简单! - Sumant
5
我更倾向于使用专业化技术来匹配它:template<typename M, typename ...L> struct C : std::false_type {}; template<typename M, typename ...L> struct C<M, M, L...> : std::true_type {}; template<typename M, typename L1, typename ...L> struct C<M, L1, L...> : C<M, L...> {}; - Johannes Schaub - litb

2
如果您想避免手动类型递归,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和逻辑或来做一些更直观的事情呢? - Sumant
这有所改善,但仍需内化繁琐的细节。 - Sumant
@Alexandre:谢谢。实际上我一直在做一些函数式编程,只是想知道确切的名称!我记不清了,因为Python称其为“reduce”,而C++则称其为“accumulate”。 - Potatoswatter
@Potatoswatter:zip函数接受两个列表作为输入,返回一组由这两个列表中的元素组成的二元组列表(因此得名“zip”)。 - Alexandre C.

2
自从C++17以来,你可以通过使用std::is_same<What, Args>...的普通包展开来继承std::disjunction,它在所有std::is_same之间执行逻辑OR操作。你的is_present类型特性将具有一个持有结果的static constexpr bool value成员变量。这与对||进行折叠表达式不同,因为如果找到匹配项,它会短路实例化其余的is_same<>::value。你可以在下面的C++11/14实现中看到它是如何工作的。
#include <type_traits>

template<class What, class... Args>
struct is_present : std::disjunction<std::is_same<What, Args>...> {};

使用C++11或C++14,你可以使用std::conditional定义自己的disjunction,并在定义is_present时使用该类型特性而不是std::disjunction
template<class...> struct disjunction : std::false_type {};
template<class T> struct disjunction<T> : T {};
template<class T, class... Ts>
struct disjunction<T, Ts...> :
    std::conditional<bool(T::value),
                     T, // no `is_same<>::value` instantiations for the Ts...
                     disjunction<Ts...>>::type {};

自C++14版本起,您还可以创建一个辅助变量模板:

template<class... Ts>
constexpr bool is_present_v = is_present<Ts...>::value;

int main() {
    std::cout << is_present_v<int, double, int> << '\n'; // prints 1
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接