Проблемы с компилятором SFINAE
-
30-09-2019 - |
Вопрос
Следующий мой код должен определить, 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?Может ли кто-нибудь протестировать на другом компиляторе?Спасибо!
Решение 4
У Стефана Т. Лававе это сказать:
Обратите внимание, что технически запрещено принимать адрес функции стандартной библиотеки. (Они могут быть перегружены, делая
&foo::bar
неоднозначно, и они могут иметь дополнительные аргументы по умолчанию, побеждая попытки устранитьstatic_cast
.)
Так что я думаю, я собираюсь использовать более простую версию, которая проверяет только для вложенного const_iterator
тип.
Другие советы
Итак, вот как я отлаживаю эти вещи.
Сначала закомментируйте отрицательную альтернативу, чтобы получить ошибку, а не просто несоответствие.Затем попробуйте создать экземпляр типа, который вы помещаете в функцию, с одним из элементов, который не работает.
На этом этапе мне удалось создать экземпляр вашего объекта sfinae, но он все еще не работал.«Это позволяет мне знать, что это ошибка против VS, поэтому вопрос в том, как ее исправить». - ОБС
Кажется, у VS проблемы с SFINAE, когда все сделано так, как вы. Конечно, это так! Это работает лучше, когда вы обертываете свой объект 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 (ЛЮБОЙ компилятор), чтобы иметь возможность определить, какой end() мне нужен. static_cast исправляет это:
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 — это все, что вам нужно, но я предоставил вам это выяснить.
Я полагаю, что реальный вопрос теперь в том, какой компилятор прав?Я делаю ставку на то, что будет последовательным:г++. Укажи на мудрого: НИКОГДА предположим, что я сделал тогда.
Редактировать:Боже... Вы неправы!
Исправленная версия:
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++ должен был потерпеть неудачу по причине, которую я подчеркнул выше.
Почему вы собираетесь все эти усилия? Если вы хотите проверить, 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. Лучший подход (ставит проверку существования в типов аргументов, которые делать участвовать в перегрузке):
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)) };
};
С 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;
};
Живой пример на идее. То int
а также long
Параметры только для устранения разрешения перегрузки, когда контейнер предлагает как (или если iterator
является typedef const_iterator iterator
, подобно std::set
разрешено) - буквальный 0
имеет тип int
и заставляет первую перегрузку выбрать.
Это, вероятно, должно быть комментарий, но у меня нет достаточно очков
@Msalters.
Хотя ваш is_container
Работает (почти), и я сам использовал свой код, я обнаружил две проблемы в этом.
Во-первых, этот тип deque<T>::iterator
обнаружен как контейнер (в GCC-4.7). Кажется, что deque<T>::iterator
имеет begin
/end
члены и const_iterator
тип определен.
2-я проблема в том, что этот код недействителен в соответствии с GCC DEV. I QUTE: Значения аргументов по умолчанию не являются частью типа функций и не участвуют в вычете. Отказ Видеть GCC BUG 51989.
Я сейчас использую это (Только 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 };
};