2016-01-22 24 views
6

Ich habe Typen gesehen, die entsprechende to_string() Funktion haben, aber operator<<() nicht überladen haben. Also, wenn Sie in den Stream einfügen, muss man , was ausführlich ist. Ich frage mich, ob es möglich ist, eine generische Funktion zu schreiben, die Benutzer operator<<() wenn unterstützt und fällt zurück auf << to_string() wenn nicht.Fallback to_string() wenn operator <<() fehlschlägt

Antwort

9

SFINAE Overkill Versuchen Sie, verwenden Sie ADL.

Der Trick ist, um sicherzustellen, dass einoperator<< zur Verfügung steht, nicht unbedingt die von der Typdefinition mitgelieferten:

namespace helper { 
    template<typename T> std::ostream& operator<<(std::ostream& os, T const& t) 
    { 
     return os << to_string(t); 
    } 
} 
using helper::operator<<; 
std::cout << myFoo; 

Dieser Trick häufig in generischem Code verwendet wird, die zwischen std::swap<T> wählen muss und eine spezialisierte Foo::swap(Foo::Bar&, Foo::Bar&).

+0

Ich stimme zu, das ist einfacher für den Fall, wo Sie nur 'Operator <<' für Typ 'T' in seinem' Namespace' oder sonst 'to_string (T)' und nichts mehr, was zugegebenermaßen ist, was das OP hat gefragt, also +1. Wenn Sie weiter versenden müssen, wird dies nicht funktionieren. Außerdem sind die Fehlermeldungen, die von dieser Lösung generiert werden, möglicherweise nicht so hilfreich wie sie sein könnten. – 5gon12eder

+0

Das ist nett. Aber dann muss ich 'operator <<()' für jeden Typ, der nur 'to_string()' überladen hat, überladen. Ich möchte solche langweilige Arbeit vermeiden. – Lingxi

+0

@ling was? Warum denkst du, dass du das tun musst? – Yakk

1

Ja, es ist möglich.

#include <iostream> 
#include <sstream> 
#include <string> 
#include <type_traits> 

struct streamy 
{ 
}; 

std::ostream& 
operator<<(std::ostream& os, const streamy& obj) 
{ 
    return os << "streamy [" << static_cast<const void *>(&obj) << "]"; 
} 

struct stringy 
{ 
}; 

std::string 
to_string(const stringy& obj) 
{ 
    auto oss = std::ostringstream {}; 
    oss << "stringy [" << static_cast<const void *>(&obj) << "]"; 
    return oss.str(); 
} 

template <typename T> 
std::enable_if_t 
< 
    std::is_same 
    < 
    std::string, 
    decltype(to_string(std::declval<const T&>())) 
    >::value, 
    std::ostream 
>& 
operator<<(std::ostream& os, const T& obj) 
{ 
    return os << to_string(obj); 
} 

int 
main() 
{ 
    std::cout << streamy {} << '\n'; 
    std::cout << stringy {} << '\n'; 
} 

Die generischen operator<< ist nur verfügbar, wenn der Ausdruck to_string(obj) ist gut getippt für obj ein const T& und hat ein Ergebnis vom Typ std::string. Wie Sie bereits in Ihrem Kommentar vermutet haben, ist dies tatsächlich SFINAE bei der Arbeit. Wenn der Ausdruck decltype nicht wohlgeformt ist, erhalten wir einen Substitutionsfehler und die Überladung verschwindet.

Dies wird jedoch wahrscheinlich Probleme mit mehrdeutigen Überlastungen bekommen. Zumindest, setzen Sie Ihre Fallback operator<< in eine eigene namespace und ziehen Sie es nur lokal über eine using Deklaration bei Bedarf. Ich denke, Sie werden besser eine benannte Funktion schreiben, die dasselbe tut.

namespace detail 
{ 

    enum class out_methods { directly, to_string, member_str, not_at_all }; 

    template <out_methods> struct tag {}; 

    template <typename T> 
    void 
    out(std::ostream& os, const T& arg, const tag<out_methods::directly>) 
    { 
    os << arg; 
    } 

    template <typename T> 
    void 
    out(std::ostream& os, const T& arg, const tag<out_methods::to_string>) 
    { 
    os << to_string(arg); 
    } 

    template <typename T> 
    void 
    out(std::ostream& os, const T& arg, const tag<out_methods::member_str>) 
    { 
    os << arg.str(); 
    } 

    template <typename T> 
    void 
    out(std::ostream&, const T&, const tag<out_methods::not_at_all>) 
    { 
    // This function will never be called but we provide it anyway such that 
    // we get better error messages. 
    throw std::logic_error {}; 
    } 

    template <typename T, typename = void> 
    struct can_directly : std::false_type {}; 

    template <typename T> 
    struct can_directly 
    < 
    T, 
    decltype((void) (std::declval<std::ostream&>() << std::declval<const T&>())) 
    > : std::true_type {}; 

    template <typename T, typename = void> 
    struct can_to_string : std::false_type {}; 

    template <typename T> 
    struct can_to_string 
    < 
    T, 
    decltype((void) (std::declval<std::ostream&>() << to_string(std::declval<const T&>()))) 
    > : std::true_type {}; 

    template <typename T, typename = void> 
    struct can_member_str : std::false_type {}; 

    template <typename T> 
    struct can_member_str 
    < 
    T, 
    decltype((void) (std::declval<std::ostream&>() << std::declval<const T&>().str())) 
    > : std::true_type {}; 

    template <typename T> 
    constexpr out_methods 
    decide_how() noexcept 
    { 
    if (can_directly<T>::value) 
     return out_methods::directly; 
    else if (can_to_string<T>::value) 
     return out_methods::to_string; 
    else if (can_member_str<T>::value) 
     return out_methods::member_str; 
    else 
     return out_methods::not_at_all; 
    } 

    template <typename T> 
    void 
    out(std::ostream& os, const T& arg) 
    { 
    constexpr auto how = decide_how<T>(); 
    static_assert(how != out_methods::not_at_all, "cannot format type"); 
    out(os, arg, tag<how> {}); 
    } 

} 

template <typename... Ts> 
void 
out(std::ostream& os, const Ts&... args) 
{ 
    const int dummy[] = {0, ((void) detail::out(os, args), 0)...}; 
    (void) dummy; 
} 

Dann verwenden Sie es so.

int 
main() 
{ 
    std::ostringstream nl {"\n"}; // has `str` member 
    out(std::cout, streamy {}, nl, stringy {}, '\n'); 
} 

Die Funktion decide_how gibt Ihnen die volle Flexibilität bei der Entscheidung, wie man einen bestimmten Typen ausgeben, auch wenn es verfügbar mehr Optionen. Es ist auch einfach zu erweitern. Zum Beispiel haben einige Typen eine str Mitgliedsfunktion anstelle einer ADL-findbaren to_string freien Funktion. (Eigentlich tat ich das schon.)

Die Funktion detail::out verwendet tag dispatching, um die entsprechende Ausgabemethode auszuwählen.

Die can_HOW Prädikate werden mit der void_t trick implementiert, die ich sehr elegant finde. Die variable out Funktion verwendet die “for each argument” trick, die ich noch eleganter finde.

Beachten Sie, dass der Code C++ 14 ist und einen aktuellen Compiler erfordert.

+0

Ist diese SFINAE? Wenn also 'to_string (x)' kompiliert wird, existiert das 'return os << to_string (obj); 'overload und nicht anders? Könnte ich 'std :: enable_if' anstelle von' std :: conditional' verwenden? – Lingxi

+0

Ich denke, die '<< to_string (x)' Überladung, wenn '<< x 'nicht kompilieren wäre nett, wenn es überhaupt möglich ist. – Lingxi

+0

Ja, das ist SFINAE. Bitte sehen Sie die aktualisierte Antwort (besonders in Antwort auf Ihren zweiten Kommentar). Ich habe über 'std :: enable_if' nachgedacht, aber ich konnte keine einfache Lösung finden, also ging ich mit dem zugegebenermaßen etwas verwirrenden' std :: conditional'. – 5gon12eder

2

template <typename T> 
void print_out(T t) { 
    print_out_impl(std::cout, t, 0); 
} 

template <typename OS, typename T> 
void print_out_impl(OS& o, T t, 
        typename std::decay<decltype(
         std::declval<OS&>() << std::declval<T>() 
        )>::type*) { 
    o << t; 
} 

template <typename OS, typename T> 
void print_out_impl(OS& o, T t, ...) { 
    o << t.to_string(); 
} 

LIVE

1

Basierend auf der Antwort von @MSalters (Credits geht zu ihm), löst dieser mein Problem und sollte eine vollständige Antwort geben.

#include <iostream> 
#include <string> 
#include <type_traits> 

struct foo_t {}; 

std::string to_string(foo_t) { 
    return "foo_t"; 
} 

template <class CharT, class Traits, class T> 
typename std::enable_if<std::is_same<CharT, char>::value, 
         std::basic_ostream<CharT, Traits>&>::type 
operator<<(std::basic_ostream<CharT, Traits>& os, const T& x) { 
    return os << to_string(x); 
} 

int main() { 
    std::cout << std::string{"123"} << std::endl; 
    std::cout << foo_t{} << std::endl; 
}