2016-01-22 32 views
6

मैंने ऐसे प्रकार देखे हैं जिनके पास to_string() फ़ंक्शन है, लेकिन operator<<() को ओवरलोड नहीं किया है। इसलिए, स्ट्रीम में डालने पर, किसी को << to_string(x) होना चाहिए जो वर्बोज़ है। मैं सोच रहा हूं कि क्या एक सामान्य कार्य लिखना संभव है कि उपयोगकर्ता operator<<() समर्थित हैं और << to_string() पर वापस आते हैं।ऑपरेटर <<() विफल रहता है <<() विफल रहता है

उत्तर

9

SFINAE overkill है, ADL का उपयोग करें।

चाल सुनिश्चित करें कि एकoperator<< उपलब्ध है, बनाने के लिए है जरूरी नहीं कि एक प्रकार परिभाषा द्वारा आपूर्ति:

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

मैं मानता हूं कि यह मामला उस मामले के लिए आसान है जहां आप केवल 'ऑपरेटर <<' को ''' 'टाइप' टाइप 'टाइप करने के लिए अपने' नेमस्पेस 'या अन्यथा' to_string (टी) 'और कुछ भी नहीं चाहते हैं, जो कि, माना जाता है कि ओपी के पास क्या है के लिए पूछा, तो +1। यदि आपको आगे प्रेषण करने की आवश्यकता है, तो यह काम नहीं करेगा। साथ ही, इस समाधान द्वारा उत्पन्न त्रुटि संदेश उतना सहायक नहीं हो सकते जितना वे हो सकते हैं। – 5gon12eder

+0

यह अच्छा है। लेकिन फिर मुझे प्रत्येक प्रकार के लिए 'ऑपरेटर <<() 'अधिभार करना होगा जो केवल' to_string()' overloaded है। मैं इस तरह के कठिन काम से बचना चाहता हूँ। – Lingxi

+0

@ling क्या? आपको ऐसा क्यों लगता है कि आपको ऐसा करना है? – 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) के लिए obj एक const 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 आप निर्णय लेने से कैसे उत्पादन एक दिया प्रकार है, भले ही वहाँ कई विकल्प उपलब्ध हैं में पूरी छूट देता है। विस्तार करना भी आसान है। उदाहरण के लिए, कुछ प्रकारों में str एडीएल खोजने योग्य सक्षम to_string नि: शुल्क फ़ंक्शन के बजाय सदस्य फ़ंक्शन होता है। (असल में, मैंने पहले ही ऐसा किया है।)

फ़ंक्शन detail::out उपयुक्त आउटपुट विधि का चयन करने के लिए tag dispatching का उपयोग करता है।

can_HOW भविष्यवाणियों को void_t trick का उपयोग करके लागू किया गया है जो मुझे बहुत ही सुरुचिपूर्ण लगता है।

विविधता out फ़ंक्शन “for each argument” trick का उपयोग करता है, जो मुझे और भी सुरुचिपूर्ण लगता है।

ध्यान दें कि कोड C++ 14 है और उसे एक अद्यतित कंपाइलर की आवश्यकता होगी।

+0

क्या यह SFINAE है? तो जब 'to_string (x)' संकलित होता है, तो 'वापसी os << to_string (obj); 'अधिभार मौजूद है और अन्यथा नहीं? क्या मैं 'std :: conditional' के बजाय' std :: enable_if' का उपयोग कर सकता हूं? – Lingxi

+0

मुझे लगता है कि '<< to_string (x)' अधिभार होने पर '<< x' संकलित नहीं होता है, अगर यह संभव है तो अच्छा होगा। – Lingxi

+0

हां, यह SFINAE है। कृपया अद्यतन उत्तर देखें (विशेष रूप से आपकी दूसरी टिप्पणी के जवाब में)। मैंने 'std :: enable_if' का उपयोग करने के बारे में सोचा लेकिन मुझे सीधे-आगे समाधान नहीं मिला, इसलिए मैं स्वीकार्य रूप से कुछ हद तक भ्रमित 'std :: सशर्त' के साथ गया। – 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

@MSalters के जवाब के आधार पर (क्रेडिट उसे जाता है), यह मेरी समस्या हल करता है और एक पूर्ण उत्तर देना चाहिए।

#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; 
} 
संबंधित मुद्दे