2011-04-24 21 views
11

Quiero tener varias funciones globales to_string() sobrecargadas que toman algún tipo T y convertirlo a su representación de cadena. Para el caso general, quiero ser capaz de escribir:Usando SFINAE para verificar si hay operador global <<?

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(); 
} 

Mi implementación de has_insertion_operator hasta ahora es:

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

(Se toma prestado de this y this.) que parece funcionar . Pero ahora yo quiero tener una versión sobrecargada de to_string para los tipos que hacer no tener operator<< pero hacer tienen su propia to_string()miembro de la función, es decir:

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(); 
} 

La implementación de has_to_string es:

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

(Esta parte parece funcionar bien. Se ha adaptado de this.) Sin embargo, cuando tengo:

struct S { 
    string to_string() const { 
    return "42"; 
    } 
}; 

int main() { 
    string buf; 
    S s; 
    to_string(s, &buf); // line 104 
} 

me sale:

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’ 

Parece que SFINAE no está sucediendo. ¿Cómo escribo has_insertion_operator correctamente para que determine si está disponible un operator<< global?

FYI: Estoy usando g ++ 4.2.1 (el que se envía como parte de Xcode en Mac OS X). Además, me gustaría que el código sea solo C++ 03 estándar sin bibliotecas de terceros, por ejemplo, Boost.

Gracias!

+1

Es todo posible, pero * ¿por qué? – Potatoswatter

+2

@Potatoswatter: el por qué no es importante. Por favor, asuma que durante el resto de mi proyecto sé lo que estoy haciendo. Si debe saber, es parte del marco para pasar parámetros de cualquier tipo para formar parte de un mensaje de error localizado.Los detalles de todos los que son innecesarios para esta pregunta. Si sabe cómo hacerlo, responda la pregunta. Sería muy apreciado. –

+2

Por qué siempre es importante. – GManNickG

Respuesta

10

Debería haber sido simplemente más fiel a this respuesta. una implementación funcional es:

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

Creo que lo hace no realidad se basan en SFINAE.

+0

Maravilloso: ¿Funciona en los compiladores C++ 03? –

+0

Pruébalo y descúbrelo. –

+0

gracias por el código de todos modos :) –

1

El inicializador de value en la línea 48 no está en un contexto donde SFINAE funciona. Intenta mover la expresión a la declaración de la función.

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

Sin embargo, tengo que cuestionar la cantidad de sofisticación que se está metiendo en esto. Veo mecanismos no ortogonales que se mezclarán entre sí (to_string frente a operator<<) y escucho suposiciones erróneas que se vuelcan (por ejemplo, que operator<< es global frente a un miembro, aunque el código implementado se ve bien a ese respecto).

+1

No se compila con varios errores. El primer error es: no hay argumentos para 'probar' que dependan de un parámetro de plantilla, por lo que debe haber una declaración de 'prueba' disponible. –

+0

BTW: operador << debe ser global si va a ser un operador de inserción ya que el primer argumento debe ser un ostream &. –

+0

BTW # 2: ostream :: ostream() está protegido. –