Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE issue in creating an "is_iterable" trait - is this a gcc bug?

The following code attempts (without using c++11) to create a trait for identifying whether a type is iterable in STL fashion :

#include <iostream>
#include <vector>

template<typename C>
struct IsIterable
{
    typedef char true_type; 
    typedef long false_type; 

    template<class T> static true_type  is_beg_iterable(
        typename T::const_iterator = C().begin()); 
    template<class T> static false_type is_beg_iterable(...); 

    enum { value = sizeof(is_beg_iterable<C>()) == sizeof(true_type) }; 
};

int main() {
    std::cout << IsIterable<std::vector<int>>::value << std::endl;
}

there's also an is_end_iterable method, omitted here for brevity

The code fails with gcc 4.9.2 *(as well as older versions) and clang and succeeds in VS2012. My assertion is that the variadic argument version would always come last in overload resolution (thus there should be no ambiguity), so who's right here?

Is there a cross-platform workaround / alternative ?

I see now that newer versions of VS also reject the code, so this last question becomes more important to answer

like image 416
Nikos Athanasiou Avatar asked Dec 25 '22 21:12

Nikos Athanasiou


2 Answers

This only works if there's an actual argument to the function which would be a better match than an ellipsis conversion. Remember that parameters with default values for which there's no argument do not participate in overload resolution.

The solution is to add another parameter and pass it an argument:

template<class T> static true_type  is_beg_iterable(int,   // <- for disambiguation
    typename T::const_iterator = C().begin());

template<class T> static false_type is_beg_iterable(...); 

enum { value = sizeof(is_beg_iterable<C>(0)) == sizeof(true_type) }; 
  //                                     ^

Live example.

like image 27
jrok Avatar answered Jan 04 '23 22:01

jrok


As of C++17, an idiomatic way to define an is_iterable trait would be:

#include <type_traits>
#include <iterator>

namespace is_iterable_impl
{
    using std::begin, std::end;

    template<class T>
    using check_specs = std::void_t<
        std::enable_if_t<std::is_same_v<
            decltype(begin(std::declval<T&>())), // has begin()
            decltype(end(std::declval<T&>()))    // has end()
        >>,                                      // ... begin() and end() are the same type ...
        decltype(*begin(std::declval<T&>()))     // ... which can be dereferenced
    >;

    template<class T, class = void>
    struct is_iterable
    : std::false_type
    {};

    template<class T>
    struct is_iterable<T, check_specs<T>>
    : std::true_type
    {};
}

template<class T>
using is_iterable = is_iterable_impl::is_iterable<T>;

template<class T>
constexpr bool is_iterable_v = is_iterable<T>::value;

Live demo.


We are std::declval<T&>() so our trait works with arrays. As you can see, std::begin and std::end have an overload for arrays, it takes a reference.

like image 167
YSC Avatar answered Jan 05 '23 00:01

YSC