Использование SFINAE для проверки глобального оператора <<?
Я хочу иметь несколько перегруженных глобальных to_string()
функций, которые берут некоторый тип T
и преобразовывают его в его строковое представление. В общем случае я хочу иметь возможность писать:
template<typename T,class OutputStringType> inline
typename enable_if<!std::is_pointer<T>::value
&& has_insertion_operator<T>::value,
void>::type
to_string( T const &t, OutputStringType *out ) {
std::ostringstream o;
o << t;
*out = o.str();
}
Моя реализация has_insertion_operator
до сих пор:
struct sfinae_base {
typedef char yes[1];
typedef char no[2];
};
template<typename T>
struct has_insertion_operator : sfinae_base {
template<typename U> static yes& test( U& );
template<typename U> static no& test(...);
static std::ostream &s;
static T const &t;
static bool const value = sizeof( test( s << t ) ) == sizeof( yes ); // line 48
};
(Он заимствует из этого
и .)
Кажется, это работает.
Но теперь я хочу иметь перегруженную версию to_string
для типов, которые не имеют operator<<
, но имеют свою собственную функцию-член to_string()
, т.е.:
template<class T,class OutputStringType> inline
typename enable_if<!has_insertion_operator<T>::value
&& has_to_string<T,std::string (T::*)() const>::value,
void>::type
to_string( T const &t, OutputStringType *out ) {
*out = t.to_string();
}
Реализация has_to_string
:
#define DECL_HAS_MEM_FN(FN_NAME) \
template<typename T,typename S> \
struct has_##FN_NAME : sfinae_base { \
template<typename SignatureType,SignatureType> struct type_check; \
template<class U> static yes& test(type_check<S,&U::FN_NAME>*); \
template<class U> static no& test(...); \
static bool const value = sizeof( test<T>(0) ) == sizeof( yes ); \
}
DECL_HAS_MEM_FN( to_string );
(Эта часть, кажется, работает отлично. Она адаптирована из этого.)
Однако, когда у меня есть:
struct S {
string to_string() const {
return "42";
}
};
int main() {
string buf;
S s;
to_string( s, &buf ); // line 104
}
Я получаю:
foo.cpp: In instantiation of ‘const bool has_insertion_operator<S>::value’:
foo.cpp:104: instantiated from here
foo.cpp:48: error: no match for ‘operator<<’ in ‘has_insertion_operator<S>::s << has_insertion_operator<S>::t’
Кажется, что SFINAE не происходит. Как написать has_insertion_operator
правильно, чтобы определить, доступен ли глобальный operator<<
?
FYI: Я использую g++ 4.2.1 (тот, который поставляется как часть Xcode в Mac OS X).
Кроме того, я бы хотел, чтобы код был только стандартным С++ 03 без сторонних библиотек, например Boost.
Спасибо!
Ответы
Ответ 1
Я должен был просто быть более верным для ответа этого.
Рабочая реализация:
namespace has_insertion_operator_impl {
typedef char no;
typedef char yes[2];
struct any_t {
template<typename T> any_t( T const& );
};
no operator<<( std::ostream const&, any_t const& );
yes& test( std::ostream& );
no test( no );
template<typename T>
struct has_insertion_operator {
static std::ostream &s;
static T const &t;
static bool const value = sizeof( test(s << t) ) == sizeof( yes );
};
}
template<typename T>
struct has_insertion_operator :
has_insertion_operator_impl::has_insertion_operator<T> {
};
Я считаю, что на самом деле он не полагается на SFINAE.
Ответ 2
Инициализатор value
в строке 48 не находится в контексте, в котором работает SFINAE. Попробуйте переместить выражение в объявление функции.
#include <iostream>
struct sfinae_base {
typedef char yes[1];
typedef char no[2];
};
template<typename T>
struct has_insertion_operator : sfinae_base {
// this may quietly fail:
template<typename U> static yes& test(
size_t (*n)[ sizeof( std::cout << * static_cast<U*>(0) ) ] );
// "..." provides fallback in case above fails
template<typename U> static no& test(...);
static bool const value = sizeof( test<T>( NULL ) ) == sizeof( yes );
};
Тем не менее, я должен задать вопрос об объеме сложности, которая идет на это. Я вижу неортогональные механизмы, которые будут размалывать друг против друга (to_string
vs. operator<<
), и я слышу плохие предположения, которые бросают вокруг (например, что operator<<
является глобальным или членом, хотя реализованный код выглядит нормально в этом отношении).