35

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

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

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

我在搜索时找到了其他编程语言的技术。然而仍然必须找出如何在 C++ 中做到这一点。

4

6 回答 6

34

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

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

活生生的例子

于 2015-04-14T18:35:18.240 回答
13

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

话虽如此,它所检查的只是 and 的声明begin() constend() const因此,即使以下也被验证为可迭代:

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

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

你可以在这里看到这些片段

于 2018-12-29T05:49:46.100 回答
7

如果您在 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.
  • 当表达式起作用时,两个重载都是候选的,所以我必须强制进行更好的专业化。在这种情况下,42has type int,因此int是比 get 更好的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如果您需要更多通用性,表达某事物可迭代的方式还可以包括用户定义的类型,这些类型为and带来自己的重载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; }

您可以使用这种技术来获得更适合您的实际环境的解决方案。

于 2019-09-05T19:27:02.303 回答
6

的,使用此特征类兼容的

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兼容类型
  • elsecheck<C>(0)调用check(...)(见省略号转换
  • sizeof(check<C>(0))取决于这些函数的返回类型
  • 最后,编译器将常量设置valuetruefalse

查看在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)提供了许多特征类,但没有关于可迭代性...

另请参阅jrokJarod42的类似答案。

这个答案在公共领域 - CC0 1.0 Universal

于 2015-04-14T17:47:31.937 回答
5

这取决于您所说的“可迭代”是什么意思。它在 C++ 中是一个松散的概念,因为您可以通过许多不同的方式实现迭代器。

如果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
于 2012-12-11T23:35:03.800 回答
1

或者,如果(像我一样)你讨厌每个 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()成员函数。

如果您想要更健壮/彻底/可重用的东西,那么请改用其他出色的建议解决方案之一。

于 2019-08-28T23:57:58.827 回答