Check if a variable type is iterable?

后端 未结 6 998
失恋的感觉
失恋的感觉 2020-11-29 02:16

Is there any way to check if an arbitrary variable type is iterable?

So to check if it has indexed elements or I can actually loop over it\'s children? (Use foreach

相关标签:
6条回答
  • 2020-11-29 02:50

    cpprefence has an example answering your question. It is using SFINAE, here is a slightly modified version of that example (in case the content of that link gets changed over time):

    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;
    

    Now, this is how it can be used

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

    Output:

    true
    true
    false
    false
    

    Having said that, all it checks is, the declaration of begin() const and end() const, so accordingly, even following is verified as an iterable:

    struct Container
    {
      void begin() const;
      void end() const;
    };
    
    std::cout << is_iterable_v<Container> << '\n'; // prints true
    

    You can see these pieces together here

    0 讨论(0)
  • 2020-11-29 02:56

    It depends on what you mean by "iterable". It is a loose concept in C++ since you could implement iterators in many different ways.

    If by foreach you're referring to C++11's range-based for loops, the type needs begin() and end() methods to be defined and to return iterators that respond to operator!=, operator++ and operator*.

    If you mean Boost's BOOST_FOREACH helper, then see BOOST_FOREACH Extensibility.

    If in your design you have a common interface that all iterable containers inherit from, then you could use C++11's 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
    
    0 讨论(0)
  • 2020-11-29 02:58

    If you are under the umbrella of C++11 and beyond, one usual way of SFINAE checking that works when you have to specialize for just one property, is the following one:

    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 */ };
    

    The trick is as follow:

    • When the expression doesn't work, only the second specialization will be instantiated, because the first will fail to compile and sfinae plays out. So you get false.
    • When the expression works, both overloads are candidate, so I have to force a better specialization. In this case, 42 has type int, and thus int is a better match than unsigned, getting true.
    • I take 42 because it's the answer to everything, inspired by Eric Niebler's range implementation.

    In your case, C++11 has the free functions std::begin and std::end that works for arrays and containers, so the expression that must work is:

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

    If you need more generality, a way to express that something is iterable could also include user-defined types that brings their own overloads for begin and end, so you need to apply some adl here:

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

    You can play with this technique to achieve solutions that fits better your actual context.

    0 讨论(0)
  • 2020-11-29 02:59

    Or if (like me) you hate every SFINAE solution being a big block of dummy struct definitions with ::type and ::value nonsense to wade through, here's an example of using a quick and (very) dirty one-liner:

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

    The last template argument does multiple things in one step:

    1. Checks to see if the type has a begin() member function, or equivalent.
    2. Checks that the begin() function returns something that has operator*() defined (typical for iterators).
    3. Determines the type that results from de-referencing the iterator, and saves it in case it's useful in your template implementation.

    Limitation: Doesn't double-check that there's a matching end() member function.

    If you want something more robust/thorough/reusable, then go with one of the other excellent proposed solutions instead.

    0 讨论(0)
  • 2020-11-29 03:04

    Yes using this traits class compatible 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) }; 
    };
    

    Explanation

    • check<C>(0) calls check(int,const_iterator) if C::end() exists and returns a const_iterator compatible type
    • else check<C>(0) calls check(...) (see ellipsis conversion)
    • sizeof(check<C>(0)) depends on the return type of these functions
    • finally, the compiler sets the constant value to true or false

    See compilation and test run on 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';
    }
    

    Output

    set=1
    int=0
    

    Note: C++11 (and C++14) provides many traits classes but none about iterablility...

    See also similar answers from jrok and Jarod42.

    This answer is in Public Domain - CC0 1.0 Universal

    0 讨论(0)
  • 2020-11-29 03:06

    You may create a trait for that:

    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.

    0 讨论(0)
提交回复
热议问题