Match iterable types (arrays and classes with begin()/end())

c++, c++14, templates, type-traits

Solution

First, some boilerplate to do easy argument dependent lookup of `begin` in a context where `std::begin` is visible:

#include <utility>
#include <iterator>
namespace adl_details {
  using std::begin; using std::end;
  template<class R>
  decltype(begin(std::declval<R>())) adl_begin(R&&r){
    return begin(std::forward<R>(r));
  }
  template<class R>
  decltype(end(std::declval<R>())) adl_end(R&&r){
    return end(std::forward<R>(r));
  }
}
using adl_details::adl_begin;
using adl_details::adl_end;

This is required to reasonably emulate how range-based `for(:)` loops find their begin/end iterators. By packaging it up like this, we reduce boilerplate below.

Next, some C++1y style utility aliases:

template<class>struct sink {using type=void;};
template<class X>using sink_t=typename sink<X>::type;
template<bool b, class T=void>using enable_if_t=typename std::enable_if<b,T>::type;

`sink_t` takes any type, and throws it away replacing it with `void`.

`enable_if_t` removes annoying `typename` spam below.

In an industrial strength library, we'd put this in `detail`s, and have a 1-type-argument version that dispatches to it. But I don't care:

template<class I,class=void> struct is_iterator:std::false_type{};
template<> struct is_iterator<void*,void>:std::false_type{};
template<> struct is_iterator<void const*,void>:std::false_type{};
template<> struct is_iterator<void volatile*,void>:std::false_type{};
template<> struct is_iterator<void const volatile*,void>:std::false_type{};
template<class I>struct is_iterator<I,
  sink_t< typename std::iterator_traits<I>::value_type >
>:std::true_type{};

`is_iterator` doesn't do heavy auditing of the `iterator_traits` of `I`. But it is enough.

template<class R>
using begin_t=decltype(adl_begin(std::declval<R&>()));
template<class R>
using end_t=decltype(adl_end(std::declval<R&>()));

These two type aliases make the stuff below less annoying.

Again, in industrial strength libraries, put 2-arg-with-`void` into `details`:

template<class R,class=void> struct has_iterator:std::false_type{};
template<class R>
struct has_iterator<
  R,
  enable_if_t<
    is_iterator<begin_t<R>>::value
    && is_iterator<end_t<R>>::value
    // && std::is_same<begin_t<R>,end_t<R>>::value
  >
>:std::true_type{};

Note the commented out line in the `enable_if_t` above. I left that out to allow asymmetric iteration to work, where the `end` is a type that has a different `operator==` overload. Such is being considered for C++17: it allows really, really efficient algorithms on null-terminated strings (for example).

Finally, the final output:

template<class R>using iterator_t=enable_if_t<has_iterator<R>::type, begin_t<R>>;

which evaluates to the iterator of the iterable range `R` iff it has one.

There are cases where this won't work, but they are pathological.

live example

Problem

I wrote type traits like classes that can be used test if a given type is "iterable". This is true for arrays (for `T[N]`, not for `T[]`) and for classes that have a `begin` and an `end` method that return things that look like iterators. I wonder if it can be done more concise/simpler than I did it? Especially the stuff in the `impl` namespace look a bit roundabout/hacky. It all looks a bit ugly to me. For an example that uses this and can be compiled with g++ and clang++ see: https://gist.github.com/panzi/869728c9879dcd4fffa8 ``` template<typename T> struct is_iterator { private: template<typename I> static constexpr auto test(void*) -> decltype( *std::declval<const I>(), std::declval<const I>() == std::declval<const I>(), std::declval<const I>() != std::declval<const I>(), ++ (*std::declval<I*>()), (*std::declval<I*>()) ++, std::true_type()) { return std::true_type(); } template<typename I> static constexpr std::false_type test(...) { return std::false_type(); } public: static constexpr const bool value = std::is_same<decltype(test<T>(0)), std::true_type>::value; }; namespace impl { // implementation details template<typename T> struct has_iterable_methods { private: template<typename C> static constexpr auto test(void*) -> decltype( std::declval<C>().begin(), std::declval<C>().end(), std::true_type()) { return std::true_type(); } template<typename C> static constexpr std::false_type test(...) { return std::false_type(); } public: static constexpr const bool value = std::is_same<decltype(test<T>(0)), std::true_type>::value; }; template<typename T, bool HasIterableMethods> struct returns_iterators : public std::false_type {}; template<typename T> struct returns_iterators<T, true> { typedef decltype(std::declval<T>().begin()) begin_type; typedef decltype(std::declval<T>().end()) end_type; static constexpr const bool value = std::is_same<begin_type, end_type>::value && is_iterator<begin_type>::value; }; } template<typename T> struct is_iterable : public std::integral_constant< bool, impl::returns_iterators< typename std::remove_const<T>::type, impl::has_iterable_methods<typename std::remove_const<T>::type>::value>::value> {}; template<typename T, std::size_t N> struct is_iterable<T[N]> : public std::true_type {}; template<typename T> struct is_iterable<T*> : public std::false_type {}; ```

Original source

Related problems