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