如何编写一个特征来检查类型是否可迭代

问题描述 投票:0回答:6

有什么方法可以检查任意变量类型是否可迭代?

那么要检查它是否有索引元素,或者我实际上可以循环它的子元素? (例如使用foreach?)

是否可以为此创建一个通用模板?

我在搜索时发现了其他编程语言的技术。但仍然需要找出如何在 C++ 中做到这一点。

c++ templates type-traits iterable
6个回答
40
投票

你可以为此创建一个特质:

namespace detail
{
    // To allow ADL with custom begin/end
    using std::begin;
    using std::end;

    template <typename T>
    auto is_iterable_impl(int)
    -> decltype (
        begin(std::declval<T&>()) != end(std::declval<T&>()), // begin/end and operator !=
        void(), // Handle evil operator ,
        ++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
        void(*begin(std::declval<T&>())), // operator*
        std::true_type{});

    template <typename T>
    std::false_type is_iterable_impl(...);

}

template <typename T>
using is_iterable = decltype(detail::is_iterable_impl<T>(0));

实例.


17
投票

cpprefence 有一个回答您问题的示例。它使用 SFINAE,这是该示例的稍微修改的版本(以防该链接的内容随着时间的推移而改变):

template <typename T, typename = void>
struct is_iterable : std::false_type {};

// this gets used only when we can call std::begin() and std::end() on that type
template <typename T>
struct is_iterable<T, std::void_t<decltype(std::begin(std::declval<T&>())),
                                  decltype(std::end(std::declval<T&>()))
                                 >
                  > : std::true_type {};

// Here is a helper:
template <typename T>
constexpr bool is_iterable_v = is_iterable<T>::value;

现在,这就是它的使用方法

std::cout << std::boolalpha;
std::cout << is_iterable_v<std::vector<double>> << '\n';
std::cout << is_iterable_v<std::map<int, double>> << '\n';
std::cout << is_iterable_v<double> << '\n';
struct A;
std::cout << is_iterable_v<A> << '\n';

输出:

true
true
false
false

话虽如此,它检查的只是

begin() const
end() const
的声明,因此,即使以下内容也被验证为可迭代:

struct Container
{
  void begin() const;
  void end() const;
};

std::cout << is_iterable_v<Container> << '\n'; // prints true

您可以在这里

一起看到这些作品

8
投票

如果您使用 C++11 及更高版本,当您必须专门针对一个属性时,SFINAE 检查的一种常用方法是以下一种:

template<class T, class = decltype(<expression that must compile>)>
inline constexpr bool expression_works(int) { return true; }

template<class>
inline constexpr bool expression_works(unsigned) { return false; }

template<class T, bool = expression_works<T>(42)>
class my_class;

template<class T>
struct my_class<T, true>
{ /* Implementation when true */ };

template<class T>
struct my_class<T, false>
{ /* Implementation when false */ };

窍门如下:

  • 当表达式不起作用时,只会实例化第二个特化,因为第一个特化将无法编译并且 sfinae 会发挥作用。所以你得到
    false
  • 当表达式起作用时,两个重载都是候选者,所以我必须强制进行更好的专业化。在本例中,
    42
    的类型为
    int
    ,因此
    int
    unsigned
    更好匹配,得到
    true
  • 我选择
    42
    ,因为它是“一切问题的答案”,受到 Eric Niebler 范围实现的启发。
  • 在您的情况下,
C++11

具有适用于数组和容器的自由函数

std::begin
std::end
,因此必须有效的表达式是:

template<class T, class = decltype(std::begin(std::declval<T>())) inline constexpr bool is_iterable(int) { return true; } template<class> inline constexpr bool is_iterable(unsigned) { return false; }

如果您需要更多通用性,表达某些内容可迭代的方法还可以包括用户定义的类型,这些类型为 
begin

end
带来自己的重载,因此您需要在此处应用一些
adl

namespace _adl_begin { using std::begin; template<class T> inline auto check() -> decltype(begin(std::declval<T>())) {} } template<class T, class = decltype(_adl_begin::check<T>())> inline constexpr bool is_iterable(int) { return true; } template<class> inline constexpr bool is_iterable(unsigned) { return false; }

您可以使用此技术来实现更适合您实际情况的解决方案。


6
投票

使用这个traits类兼容c++03template<typename C> struct is_iterable { typedef long false_type; typedef char true_type; template<class T> static false_type check(...); template<class T> static true_type check(int, typename T::const_iterator = C().end()); enum { value = sizeof(check<C>(0)) == sizeof(true_type) }; };

说明

如果

check<C>(0)
    存在,
  • check(int,const_iterator)
    调用
    C::end()
    并返回
    const_iterator
    兼容类型
    else 
  • check<C>(0)
  • 调用
    check(...)
    (请参阅
    省略号转换
  • sizeof(check<C>(0))
  • 取决于这些函数的返回类型
    最后,编译器将常量 
  • value
  • 设置为
    true
    false
    
    
  • 参见
coliru

上的编译和测试运行 #include <iostream> #include <set> int main() { std::cout <<"set="<< is_iterable< std::set<int> >::value <<'\n'; std::cout <<"int="<< is_iterable< int >::value <<'\n'; }

输出

set=1 int=0

注意:

C++11(和 C++14)提供了许多 traits 类,但没有提供可迭代性... 另请参阅

jrok

Jarod42 的类似答案。

此答案属于公共领域 -

CC0 1.0 通用


5
投票

如果通过

foreach

您指的是 C++11 基于范围的 for 循环,则该类型需要定义

begin()
end()
方法并返回响应
operator!=
的迭代器,
operator++
operator*

如果您指的是 Boost 的 BOOST_FOREACH 助手,请参阅

BOOST_FOREACH 扩展性

如果在您的设计中您有一个所有可迭代容器都继承自的通用接口,那么您可以使用 C++11 的

std::is_base_of

: struct A : IterableInterface {} struct B {} template <typename T> constexpr bool is_iterable() { return std::is_base_of<IterableInterface, T>::value; } is_iterable<A>(); // true is_iterable<B>(); // false



1
投票
::type

::value
废话的虚拟结构定义,那么这里有一个使用快速且(非常)肮脏的单行代码的示例:

template < class Container, typename ValueType = decltype(*std::begin(std::declval<Container>()))> static void foo(Container& container) { for (ValueType& item : container) { ... } }

最后一个模板参数一步完成多项操作:

检查类型是否具有
    begin()
  1. 成员函数或等效函数。
    检查 
  2. begin()
  3. 函数是否返回已定义
    operator*()
    的内容(迭代器的典型情况)。
    确定取消引用迭代器所产生的类型,并保存它,以防它在模板实现中有用。
  4. 限制:不仔细检查是否存在匹配的
end()

成员函数。


如果您想要更强大/彻底/可重用的东西,那么请选择其他优秀的建议解决方案之一。

© www.soinside.com 2019 - 2024. All rights reserved.