Проблема SFINAE при создании трейта is_iterable — это ошибка gcc?

Следующий код пытается (без использования С++ 11) создать трейт для определения того, является ли тип итерируемым в стиле STL:

#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;
}

Есть также метод is_end_iterable, который здесь для краткости опущен

Код не работает с gcc 4.9.2 *(также как в более старых версиях) и clang и преуспевает в VS2012. Я утверждаю, что версия с переменным аргументом всегда будет последней при разрешении перегрузки (поэтому не должно быть двусмысленности), так что кто здесь прав?

Есть ли кроссплатформенный обходной путь/альтернатива?

Теперь я вижу, что более новые версии VS также отклоняют код, поэтому ответ на последний вопрос становится более важным


person Nikos Athanasiou    schedule 24.03.2015    source источник


Ответы (3)


Это работает только в том случае, если у функции есть фактический аргумент, который будет лучше соответствовать, чем преобразование с многоточием. Помните, что параметры со значениями по умолчанию, для которых нет аргументов, не участвуют в разрешении перегрузки.

Решение состоит в том, чтобы добавить еще один параметр и передать ему аргумент:

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) }; 
  //                                     ^

Живой пример.

person jrok    schedule 24.03.2015
comment
это было опубликовано незначительно после ответа Jarod42, но содержит причину, по которой компиляция не удалась - person Nikos Athanasiou; 24.03.2015

Начиная с C++17, идиоматическим способом определения признака is_iterable будет:

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

Текущая демонстрация.


Нас std::declval<T&>(), поэтому наш трейт работает с массивами. Как видите, std::begin и std::end имеют перегрузку для массивов, это занимает ссылка.

person YSC    schedule 22.11.2018

Следующие работы (по крайней мере, с gcc 4.9.2):

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

    template<class T> static true_type  is_beg_iterable(int,
        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) };
};

Живой пример.

person Jarod42    schedule 24.03.2015