2015-12-09 39 views
5

我所有的類都實現了一個dump成員函數,例如:轉儲成員函數的條款通用實現運營商<<功能

struct A { 
    template <typename charT> 
    std::basic_ostream<charT> & 
    dump(std::basic_ostream<charT> &o) const { 
     return (o << x); 
    } 
    int x = 5; 
}; 

我想一旦實施operator<<功能適用於所有這些類:

template<typename charT, typename T> 
std::basic_ostream<charT> & 
operator<< (std::basic_ostream<charT> &o, const T &t) { 
    return t.dump(o); 
} 

問題是所有類型都被此模板捕獲,包括標準類型。有沒有辦法解決這個問題?

+3

你爲什麼不只是超載''<<代替自卸這些方法?實現起來更直觀,更簡單。 – erip

+1

爲什麼不簡單地在'A'裏面重載'operator <<'? – luk32

+0

@ luk32'operator <<'不能是成員函數,可以嗎?所以'dump'成員函數更方便,因爲它們可以直接訪問數據成員(不需要使用點運算符)。 – AlwaysLearning

回答

9
template <typename T, typename charT> 
auto operator<< (std::basic_ostream<charT> & str, const T & t) -> decltype(t.dump(str)) 
{ 
    static_assert(std::is_same 
        <decltype(t.dump(str)), 
        std::basic_ostream<charT> &>::value, 
        ".dump(ostream&) does not return ostream& !"); 

    return t.dump(str); 
} 

這僅適用於定義一個適當的dump成員類型重載operator<<

編輯:添加static_assert更好的消息。

+0

在C++ 14中,你可以放棄追蹤返回類型:) – OMGtechy

+1

@OMGtechy你可以,屆時sfinae將無法工作。 – ForEveR

+0

@ForEveR從來不知道 - 謝謝! – OMGtechy

3

你可以做一個空基類,說:

struct HasDump {}; 

而且使HasDump所有類的基礎,那就是:

struct A : HasDump (... 

然後你operator<<std::enable_ifstd::is_base_of所以包它僅適用於HasDumpT的基數。

(我還沒有集中在C++爲一年或兩年那麼這個建議可能會有點生疏)

+1

這是OP問題的一個很好的答案,但我仍然認爲OP太複雜了一個非常簡單的問題。 – erip

1

通常,這將是最好的方式,IMO:

struct A { 
    int x = 5; 

    friend std::ostream & operator<<(std::ostream &os, const A& a){ 
     return (os << a.x); 
    } 
}; 

原因:'friend' functions and << operator overloading: What is the proper way to overload an operator for a class?

如果你真的想有一個專用轉儲方法,您可以定義一個基類「收集」 dumpable對象。

+0

'dump'成員函數更方便,因爲它們可以直接訪問數據成員(不需要使用點運算符)。 – AlwaysLearning

+1

有爭議的美學。你保存'a.',但是你引入了一個冗餘方法,並且你用重載的'operator <<'重載了全局名字空間。此方法在'A'的定義中包含重載運算符,編譯器仍然會查找並匹配它。 – luk32

+1

在任何情況下,您都不要將任何事物混淆在全局名稱空間中。您可以在與類相同的命名空間中定義運算符<<,並通過ADL找到它。 –

0

只是增加了這個樂趣。如果你碰巧有一個以上的方法在不同的類,打印/轉儲:

#include <iostream> 
#include <type_traits> 

namespace tests { 

    // this is a utility class to help us figure out whether a class 
    // has a member function called dump that takes a reference to 
    // an ostream 
    struct has_dump 
    { 
     // We will only be checking the TYPE of the returned 
     // value of these functions, so there is no need (in fact we 
     // *must not*) to provide a definition 
     template<class T, class Char> 
     static auto test(const T* t, std::basic_ostream<Char>& os) 
     -> decltype(t->dump(os), std::true_type()); 

     // the comma operator in decltype works in the same way as the 
     // comma operator everywhere else. It simply evaluates each 
     // expression and returns the result of the last one 
     // so if t->dump(os) is valid, the expression is equivalent to 
     // decltype(std::true_type()) which is the type yielded by default- 
     // constructing a true_type... which is true_type! 


     // The above decltype will fail to compile if t->dump(os) is not 
     // a valid expression. In this case, the compiler will fall back 
     // to selecting this next function. this is because the overload 
     // that takes a const T* is *more specific* than the one that 
     // takes (...) [any arguments] so the compiler will prefer it 
     // if it's well formed. 

     // this one could be written like this: 
     // template<class T, class Char> 
     // static std::false_type test(...); 
     // I just happen to use the same syntax as the first one to 
     // show that they are related. 

     template<class T, class Char> 
     static auto test(...) -> decltype(std::false_type()); 
    }; 

    // ditto for T::print(ostream&) const  
    struct has_print 
    { 
     template<class T, class Char> 
     static auto test(const T* t, std::basic_ostream<Char>& os) 
     -> decltype(t->print(os), std::true_type()); 

     template<class T, class Char> 
     static auto test(...) -> decltype(std::false_type()); 
    }; 
} 

// constexpr means it's evaluated at compile time. This means we can 
// use the result in a template expansion. 
// depending on whether the expression t->dump(os) is well formed or not 
// (see above) it will either return a std::true_type::value (true!) 
// or a std::false_type::value (false!) 

template<class T, class Char> 
static constexpr bool has_dump() { 
    // the reinterpret cast stuff is so we can pass a reference without 
    // actually constructing an object. remember we're being evaluated 
    // during compile time, so we can't go creating ostream objects here - 
    // they don't have constexpr constructors. 
    return decltype(tests::has_dump::test<T, Char>(nullptr, 
                *reinterpret_cast<std::basic_ostream<Char>*>(0)))::value; 
} 

template<class T, class Char> 
static constexpr bool has_print() { 
    return decltype(tests::has_print::test<T, Char>(nullptr, 
                *reinterpret_cast<std::basic_ostream<Char>*>(0)))::value; 
} 

// so now we can use our constexpr functions has_dump<> and has_print<> 
// in a template expansion, because they are known at compile time. 
// std::enable_if_t will ensure that the template function is only 
// well formed if our condition is true, so we avoid duplicate 
// definitions. 
// the use of the alternative operator representations make this 
// a little more readable IMHO: http://en.cppreference.com/w/cpp/language/operator_alternative 

template<class T, class Char> 
auto operator<< (std::basic_ostream<Char>& os, const T& t) 
-> std::enable_if_t< has_dump<T, Char>() and not has_print<T, Char>(), std::basic_ostream<Char>&> 
{ 
    t.dump(os); 
    return os; 
} 

template<class T, class Char> 
auto operator<< (std::basic_ostream<Char>& os, const T& t) 
-> std::enable_if_t< has_print<T, Char>() and not has_dump<T, Char>(), std::basic_ostream<Char>&> 
{ 
    t.print(os); 
    return os; 
} 

template<class T, class Char> 
auto operator<< (std::basic_ostream<Char>& os, const T& t) 
-> std::enable_if_t< has_print<T, Char>() and has_dump<T, Char>(), std::basic_ostream<Char>&> 
{ 
    // because of the above test, this function is only compiled 
    // if T has both dump(ostream&) and print(ostream&) defined. 

    t.dump(os); 
    os << ":"; 
    t.print(os); 
    return os; 
} 



struct base 
{ 
    template<class Char> 
    void dump(std::basic_ostream<Char>& os) const 
    { 
     os << x; 
    } 

    int x = 5; 
}; 
namespace animals 
{ 
    class donkey : public base 
    { 

    public: 
     template<class Char> 
     void dump(std::basic_ostream<Char>& s) const { 
      s << "donkey: "; 
      base::dump(s); 
     } 
    }; 

    class horse // not dumpable, but is printable 
    { 
    public: 
     template<class Char> 
     void print(std::basic_ostream<Char>& s) const { 
      s << "horse"; 
     } 
    }; 

    // this class provides both dump() and print()   
    class banana : public base 
    { 
    public: 

     void dump(std::ostream& os) const { 
      os << "banana!?!"; 
      base::dump(os); 
     } 

     void print(std::ostream& os) const { 
      os << ":printed"; 
     } 

    }; 
} 


auto main() -> int 
{ 
    using namespace std; 

    animals::donkey d; 
    animals::horse h; 

    cout << d << ", " << h << ", " << animals::banana() << endl; 

    return 0; 
} 

預期輸出:

donkey: 5, horse, banana!?!5::printed 
+0

請問您可以在此代碼中添加一些註釋,以解釋在'decltype'中用逗號分隔的兩種類型以及所有其他高科技? – AlwaysLearning

+0

當然,很高興。 –

+0

@完成學習完成。享受車程 :-) –