2016-01-22 12 views
6

対応するto_string()の型を見たことがありますが、オーバーロードされていませんoperator<<()。したがって、ストリームに挿入するときには、<< to_string(x)が冗長である必要があります。サポートされている場合はユーザoperator<<()という汎用関数を記述することが可能かどうかは疑問ですが、そうでない場合は<< to_string()になります。operator <<()が失敗したときのto_string()へのフォールバック

答えて

9

を試してみてください。

トリックはoperator<<は、利用可能であることを確認することです必ずしも型定義によって提供される1:

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; 

このトリックは、一般的にstd::swap<T>の間で選択する必要があり、一般的なコードで使用されており、特化したFoo::swap(Foo::Bar&, Foo::Bar&)です。

+0

これは、 'namespace'または' to_string(T) 'で定義された' T'型に対して 'operator <<'だけを必要とする場合にはもっと簡単であることに同意します。尋ねられたので、+1。あなたがさらにディスパッチする必要がある場合、これは動作しません。また、このソリューションによって生成されるエラーメッセージは、可能な限り役立たない可能性があります。 – 5gon12eder

+0

これはいいですね。しかし、 'to_string()'だけをオーバーロードした型ごとに 'operator <<()'をオーバーロードしなければなりません。私はそのような退屈な仕事を避けたい。 – Lingxi

+0

何を?なぜあなたはそれをしなければならないと思いますか? – Yakk

1

はい、可能です。

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

ジェネリックoperator<<表現to_string(obj)objconst T&のためによく型付けされ、タイプstd::stringの結果を持っている場合にのみ使用可能になります。あなたのコメントであなたがすでに推測しているように、これは実際にSFINAEの職場です。 decltypeの式が整形式でないと、置換に失敗し、オーバーロードが消滅します。

しかし、これによりあいまいなオーバーロードで問題が発生する可能性があります。少なくともoperator<<を独自のnamespaceに置き、必要に応じてusing宣言を介してローカルにドラッグするだけです。私はあなたが同じことをする名前付き関数を書く方が良いと思う。

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

そして、そのようにそれを使用しています。

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

機能decide_howは、利用可能な複数のオプションがある場合でも、あなたに出力する方法を指定された型を決定する際に、完全な柔軟性を提供します。拡張も容易です。たとえば、ADLで検索可能なto_stringフリー関数ではなく、strメンバ関数を持つタイプがあります。 (実際には、すでにそれを行っています)

関数detail::outは、tag dispatchingを使用して適切な出力方法を選択します。

can_HOW述語は、私が非常にエレガントなvoid_t trickを使用して実装されています。

variadic out関数は“for each argument” trickを使用していますが、これはさらに洗練されています。

コードはC++ 14であり、最新のコンパイラが必要です。

+0

これはSFINAEですか?したがって 'to_string(x)'がコンパイルされるとき、 'return os << to_string(obj);'オーバーロードが存在し、それ以外の場合は存在しませんか? 'std :: conditional'の代わりに' std :: enable_if'を使うことができますか? – Lingxi

+0

'<< x'がコンパイルされない場合、オーバーロードが発生すると、' << to_string(x) 'オーバーロードが発生するといいでしょう。 – Lingxi

+0

はい、これはSFINAEです。更新された答えを見てください(特に、2番目のコメントに返信してください)。私は 'std :: enable_if'を使うことを考えましたが、簡単な解決策を見つけることができなかったので、かなり混乱している' std :: conditional'と一緒に行きました。 – 5gon12eder

2

は、ADLを使用し、SFINAEが過剰です

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

@MSalters(クレジットは彼に行く)の答えに基づいて、この1つは私の問題を解決し、完全な答えを出すべきです。

#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; 
} 
関連する問題