How to write a trait which checks whether a type is iterable
Asked Answered
J

6

40

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 for example?)

Is it possible to create a universal template for that?

I've found techniques for other programming languages while searching for it. Yet still have to find out how to do this in C++.

Jankell answered 11/12, 2012 at 23:19 Comment(4)
Assuming iterability is expressed as a type, maybe RTTI? en.wikibooks.org/wiki/C%2B%2B_Programming/RTTIExpendable
Could you provide an example or two of what you're asking?Dhammapada
I'm using the Maya API and it's classes. You can iterate over its MVectorArray, MIntArray classes, but also the MVector, MPoint, MColor because they contain more than a single numerical value (or string for the MString/MStringArray). Now I'm trying to create a template that supports adding (or multiplying, or dividing and more generic mathematical equations) any of the arbitrary values together. Which would mean for iterable elements to iterate over them and add a single element to each of its elements/indices. Does that make any sense?Jankell
possible duplicate of Match iterable types (arrays and classes with begin()/end())Atelier
C
5

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
Congressional answered 11/12, 2012 at 23:35 Comment(0)
C
43

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.

In C++20, with concept, it is even more natural:

namespace detail
{
    // To allow ADL with custom begin/end
    using std::begin;
    using std::end;
 
    template <typename T>
    concept is_iterable_impl = requires(T& t)
    {
        begin(t) != end(t); // begin/end and operator !=
        ++std::declval<decltype(begin(t))&>(); // operator ++
        *begin(t); // operator*
    };
}

template <typename T>
concept is_iterable = detail::is_iterable_impl<T>;

Demo

Comus answered 14/4, 2015 at 18:35 Comment(8)
This is such a good example, it tought me a real good use of std::declval. Thank you very much!Cubiform
this is SFINE for functions, and with decltype like above is the most elegant way for many different problems (why do we need any other way? use this and enable_if for everything). There is just one side note I have seen here in the comments about the need to stuff void() to break overloading operator,. It is not needed for this answer, but in general may be necessary in some cases.Zeller
@Jarod42, why do you have an int as the argument type in the declaration auto is_iterable_impl(int)? Also, what is the evil operator here?Desireah
@user2635088: int is used to provide order when both overload are viable. f(int) is a better match than f(...).Comus
We can overload operator ,, doing so might invalidate the test whereas it would be valid. (No match for A(), B() but A() and B() are both valid).Comus
Thank you for this great answer! Without it, I would never be able to create this project.Condescension
This is a good example I found in my search. But I am looking for a way to determine if a variable (or object) is iterable. So, something like auto n; is_iterable(n). Is that possible? If yes then how?Delp
@AbdurRakib: template <typename T> bool is_iterable_func(const T&) { return is_iterable<T>::value; } or is_iterable<decltype(n)>::value?Comus
L
19

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

Laurasia answered 29/12, 2018 at 5:49 Comment(2)
the question was explicitly asked for C++Laurasia
@Youda008 Now it works for plain C arrays too.Aero
N
8

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.

Niles answered 5/9, 2019 at 19:27 Comment(0)
D
6

Yes using this traits class compatible

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

Delegacy answered 14/4, 2015 at 17:47 Comment(0)
C
5

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
Congressional answered 11/12, 2012 at 23:35 Comment(0)
F
1

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.

Favus answered 28/8, 2019 at 23:57 Comment(1)
And in addition, what you propose here is bad style. You write the metafunction inline, intermingled with your implementation code. If you happen to need it several times, you will end up duplicating the metafunction code into each and every usage site. In what way do you think that is "better" than defining a clean self-contained traits template, and just call it as a library metafunction when needed? Note: the latter approach is what you put off as "nonsense to wade through"Raul

© 2022 - 2024 — McMap. All rights reserved.