c++ 检查变量类型是否可迭代?

j8yoct9x  于 2023-02-14  发布在  其他
关注(0)|答案(6)|浏览(178)

有没有办法检查一个任意的变量类型是否是可迭代的?
所以检查它是否有索引元素或者我可以循环遍历它的子元素吗?(例如使用foreach?)
是否有可能为此创建一个通用模板?
我在寻找其他编程语言的时候发现了一些技术,但仍然需要找到如何在C++中做到这一点。

wxclj1h5

wxclj1h51#

你可以为此创造一个特性:

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.

qij5mzcb

qij5mzcb2#

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

ekqde3dh

ekqde3dh3#

如果你在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
  • 当表达式工作时,两个重载都是候选的,所以我必须强制一个更好的专门化,在这个例子中,42int类型,因此intunsigned更匹配,得到true
  • 我选择42是因为它是所有问题的答案,灵感来自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带来自己的重载,所以你需要在这里应用一些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; }

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

eagi6jfj

eagi6jfj4#

    • 是**使用此特性类兼容c++03
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) }; 
};

解释

  • 如果C::end()存在,则check<C>(0)调用check(int,const_iterator)并返回const_iterator兼容类型
  • 否则check<C>(0)调用check(...)(请参阅省略号转换)
  • 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
    • 注意:**C11(和C14)提供了许多traits classes,但没有关于可迭代性的...

另请参见jrokJarod42的类似回答。
此答案位于公共域中-CC0 1.0 Universal

jmp7cifd

jmp7cifd5#

这取决于你所指的“可迭代”。它在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
jljoyd4f

jljoyd4f6#

或者,如果(像我一样)你讨厌每一个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()成员函数或等效函数。
1.检查begin()函数是否返回定义了operator*()的内容(通常用于迭代器)。
1.确定取消引用迭代器所产生的类型,并保存它,以防它在模板实现中有用。
限制:不仔细检查是否存在匹配的end()成员函数。
如果您想要更健壮、更彻底、更可重用的解决方案,那么可以使用其他优秀的解决方案。

相关问题