SFINAE проблемы с компилятором - PullRequest
7 голосов
/ 03 декабря 2010

Следующий мой код должен определить, имеет ли T методы begin и end:

template <typename T>
struct is_container
{
    template <typename U, typename U::const_iterator (U::*)() const,
                          typename U::const_iterator (U::*)() const>
    struct sfinae {};

    template <typename U> static char test(sfinae<U, &U::begin, &U::end>*);
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};

А вот некоторый тестовый код:

#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>

int main()
{
    std::cout << is_container<std::vector<std::string> >::value << ' ';
    std::cout << is_container<std::list<std::string> >::value << ' ';
    std::cout << is_container<std::set<std::string> >::value << ' ';
    std::cout << is_container<std::map<std::string, std::string> >::value << '\n';
}

Вклg ++ 4.5.1, вывод 1 1 1 1.Однако в Visual Studio 2008 вывод будет 1 1 0 0.Я сделал что-то не так, или это просто ошибка VS 2008?Кто-нибудь может проверить на другом компиляторе?Спасибо!

Ответы [ 5 ]

12 голосов
/ 03 декабря 2010

Итак, вот как я отлаживаю эти вещи.

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

На этом этапе я смог создать экземпляр вашего объекта sfinae, но он все еще не работал. «Это дает мне понять, что это ошибка VS, поэтому вопрос в том, как ее исправить». - OBS

Кажется, у VS проблемы с SFINA, когда все сделано так, как ты. Конечно, это так! Это работает лучше, когда вы заверните свой объект sfinae. Я сделал это так:

template <typename U, typename it_t = typename U::const_iterator >
struct sfinae 
{
  // typedef typename U::const_iterator it_t; - fails to compile with non-cont types.  Not sfinae
  template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const >
  struct type_ {};

  typedef type_<U,it_t,&U::begin,&U::end> type;
};

Все еще не работал, но по крайней мере я получил полезное сообщение об ошибке:

error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'std::_Tree_const_iterator<_Mytree> (__thiscall std::set<_Kty>::* )(void) const'

Это позволяет мне знать, что &U::end недостаточно для того, чтобы VS ( ANY компилятор) мог определить, какой конец () мне нужен. Статический_каст исправляет это :

  typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type;

Соберите все вместе и запустите на нем свою тестовую программу ... успех с VS2010. Вы можете обнаружить, что static_cast - это действительно все, что вам нужно, но я оставил это вам, чтобы узнать.

Полагаю, реальный вопрос сейчас в том, какой компилятор прав? Моя ставка на тот, который был последовательным: g ++. Укажите на мудрого: НИКОГДА предположите, что я тогда сделал.

Редактировать: Боже ... Вы ошибаетесь!

Исправленная версия:

template <typename T>
struct is_container
{
    template <typename U, typename it_t = typename U::const_iterator > 
    struct sfinae 
    {
      //typedef typename U::const_iterator it_t;
      template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const >
      struct type_ {};

      typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type;
    };

    template <typename U> static char test(typename sfinae<U>::type*);
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};



#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>

int main()
{
    std::cout << is_container<std::vector<std::string> >::value << ' ';
    std::cout << is_container<std::list<std::string> >::value << ' ';
    std::cout << is_container<std::set<std::string> >::value << ' ';
    std::cout << is_container<std::map<std::string, std::string> >::value << ' ';
    std::cout << is_container<bool>::value << '\n';
}

- Отладка выше разумна, но предположение о компиляторе было ошибочным. G ++ должен был потерпеть неудачу по причине, которую я подчеркнул выше.

5 голосов
/ 10 декабря 2010

Почему вы идете на все эти усилия?Если вы хотите проверить, существует ли U::begin(), почему бы не попробовать?

template <typename T>
struct is_container
{
    template <typename U> static char test(U* u,
       typename U::const_iterator b = ((U*)0)->begin(),
       typename U::const_iterator e = ((U*)0)->end());
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};

В дополнение к проверке на наличие U::begin() и U::end(), это также проверяет, возвращают ли они что-то, чтоконвертируемый в const_iterator.Это также позволяет избежать ловушки, отмеченной Стефаном Т. Лававежем, с помощью выражения вызова, которое должно поддерживаться, вместо принятия конкретной подписи.

[править] Извините, это основывалось на создании шаблона VC10.Лучший подход (ставит проверку существования в типах аргументов, которые do участвуют в перегрузке):

template <typename T> struct is_container
{
    // Is.
    template <typename U>
    static char test(U* u, 
                     int (*b)[sizeof(typename U::const_iterator()==((U*)0)->begin())] = 0,
                     int (*e)[sizeof(typename U::const_iterator()==((U*)0)->end())] = 0);
    // Is not.
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};
3 голосов
/ 08 февраля 2012

С C ++ 11 теперь есть лучшие способы обнаружить это.Вместо того чтобы полагаться на сигнатуру функций, мы просто вызываем их в контексте выражения SFINAE:

#include <type_traits> // declval

template<class T>
class is_container{
  typedef char (&two)[2];

  template<class U> // non-const
  static auto test(typename U::iterator*, int)
      -> decltype(std::declval<U>().begin(), char());

  template<class U> // const
  static auto test(typename U::const_iterator*, long)
      -> decltype(std::declval<U const>().begin(), char());

  template<class>
  static two  test(...);

public:
  static bool const value = sizeof(test<T>(0, 0)) == 1;
};

Живой пример на Ideone. Параметры int и long толькодля устранения неоднозначности разрешения перегрузки, когда контейнер предлагает оба (или если iterator равно typedef const_iterator iterator, как разрешено std::set) - литерал 0 имеет тип int и заставляет выбрать первую перегрузку.

1 голос
/ 08 февраля 2012

Вероятно, это должен быть комментарий, но мне не хватает очков

@ MSalters

Даже если ваш is_container работает (почти) и я сам использовал ваш код, я обнаружил в нем две проблемы.

Во-первых, тип deque<T>::iterator определяется как контейнер (в gcc-4.7). Кажется, что deque<T>::iterator имеет begin / end членов и определен тип const_iterator.

2-я проблема заключается в том, что этот код недействителен в соответствии с разработчиками GCC. Примечание: значения аргументов по умолчанию не являются частью типа функции и не участвуют в выводе . См ошибка GCC 51989

В настоящее время я использую this (только C ++ 11) для is_container<T>:

template <typename T>
struct is_container {
    template <
        typename U,
        typename S = decltype (((U*)0)->size()),
        typename I = typename U::const_iterator
    >
    static char test(U* u);
    template <typename U> static long test(...);
    enum { value = sizeof test<T>(0) == 1 };
};
1 голос
/ 10 декабря 2010

Стефан Т. Лававей имеет это , чтобы сказать:

Обратите внимание, что технически запрещено принимать адрес функции-члена стандартной библиотеки. (Они могут быть перегружены, что делает &foo::bar неоднозначными, и они могут иметь дополнительные аргументы по умолчанию, побеждая попытки устранить неоднозначность с помощью static_cast.)

Так что я предполагаю, что собираюсь использоватьболее простая версия, которая проверяет только вложенный тип const_iterator.

...