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 {};
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 details, 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With