检查变量是否可迭代?

时间:2012-12-11 23:19:40

标签: c++ arrays iterable

有没有办法检查任意变量类型是否可迭代?

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

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

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

6 个答案:

答案 0 :(得分:22)

您可以为此创建一个特征:

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

Live example

答案 1 :(得分:6)

这取决于你对“可迭代”的意思。它在C ++中是一个松散的概念,因为你可以用许多不同的方式实现迭代器。

如果由foreach引用C ++ 11的基于范围的for循环,则类型需要定义begin()end()方法并返回响应的迭代器operator!=operator++operator*

如果您的意思是Boost的BOOST_FOREACH助手,请参阅BOOST_FOREACH Extensibility

如果在你的设计中你有一个所有可迭代容器继承的公共接口,那么你可以使用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

答案 2 :(得分:4)

使用此traits class兼容的

template<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兼容类型
  • 其他check<C>(0)来电check(...)(请参阅ellipsis conversion
  • sizeof(check<C>(0))取决于这些函数的返回类型
  • 最后,编译器将常量value设置为truefalse

请参阅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 classes但没有关于iterablility ...

另请参阅jrokJarod42的类似答案。

此答案位于公共领域 - CC0 1.0 Universal

答案 3 :(得分:2)

cpprefence has an example answering your question。它使用的是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() constend() const的声明,因此,即使以下内容也被验证为可迭代的:

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

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

您可以here一起看到这些碎片

答案 4 :(得分:2)

如果您受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,因此intunsigned更匹配,得到true
  • 我选择42,因为它是answer to everything,受埃里克·尼布勒(Eric Niebler)的范围实现启发。

在您的情况下,C++11具有适用于数组和容器的自由函数std::beginstd::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; }

如果您需要更多的通用性,一种表示可迭代的方式还可以包括用户定义的类型,这些类型会为beginend带来自己的重载,因此您需要应用一些{{ 1}}:

adl

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

答案 5 :(得分:0)

或者,如果(像我一样)您讨厌每个SFINAE解决方案都是一大堆虚假结构定义,并且无济于事,::type::value的话,这是使用快速(非常)的示例肮脏的一线:

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

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

  1. 检查该类型是否具有begin()成员函数或等效函数。
  2. 检查begin()函数是否返回定义了operator*()的内容(对于迭代器而言通常是这样)。
  3. 确定取消引用迭代器后产生的类型,并保存该类型以防在模板实现中有用。

限制:请勿再次检查是否存在匹配的end()成员函数。

如果您想要更坚固/彻底/可重复使用的产品,请改用其他出色的建议解决方案之一。