2015-12-09 13 views
5

Tất cả các lớp học của tôi thực hiện một chức năng dump thành viên, ví dụ:thực hiện Generic của operator << chức năng về mặt chức năng viên bãi

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

Tôi muốn thực hiện một chức năng operator<< một lần cho tất cả các lớp học như:

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

Vấn đề là tất cả các loại đều bị mẫu này nắm bắt, bao gồm các loại tiêu chuẩn. Có cách nào để giải quyết vấn đề này không?

+3

Tại sao bạn không chỉ quá tải '<< 'thay vì những phương pháp bãi ? Nó trực quan và tầm thường hơn nhiều để thực hiện. – erip

+1

Tại sao không đơn giản là quá tải 'toán tử <<' bên trong 'A'? – luk32

+0

@ luk32 'operator <<' không thể là một hàm thành viên, phải không? Vì vậy, các hàm thành viên 'dump' thuận tiện hơn, vì chúng có thể truy cập trực tiếp vào các thành viên dữ liệu (mà không cần sử dụng toán tử dấu chấm). – AlwaysLearning

Trả lời

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

này quá tải operator<< chỉ dành cho loại mà xác định một dump thành viên thích hợp.

Chỉnh sửa: đã thêm static_assert cho thư tốt hơn.

+0

Trong C++ 14, bạn có thể thả loại trả về theo sau :) – OMGtechy

+1

@OMGtechy bạn có thể, lúc đó sfinae sẽ không hoạt động. – ForEveR

+0

@ForEveR Không bao giờ biết điều đó - cảm ơn! – OMGtechy

3

Bạn có thể làm cho một lớp cơ sở có sản phẩm nào, nói:

struct HasDump {}; 

Và chắc HasDump cơ sở của tất cả các lớp học của bạn, đó là:

struct A : HasDump (... 

Sau đó quấn bạn operator<< với std::enable_ifstd::is_base_of nên chỉ áp dụng khi HasDump là cơ sở của T.

(Tôi đã không tập trung vào C++ cho một hoặc hai năm để lời khuyên này có thể là một chút gỉ)

+1

Đây là một câu trả lời tốt cho vấn đề của OP, nhưng tôi vẫn nghĩ rằng OP là quá phức tạp một vấn đề rất đơn giản. – erip

1

Nói chung đây sẽ là cách khuyến khích, IMO:

struct A { 
    int x = 5; 

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

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

Nếu bạn thực sự muốn có một phương pháp bãi chuyên dụng, bạn có thể định nghĩa một lớp cơ sở "thu thập" dumpable đối tượng.

+0

Các hàm thành viên 'dump' thuận tiện hơn, vì chúng có thể truy cập trực tiếp vào các thành viên dữ liệu (mà không cần sử dụng toán tử dấu chấm). – AlwaysLearning

+1

Thẩm mỹ có thể tranh luận. Bạn lưu 'a.' nhưng bạn giới thiệu một phương thức dự phòng, và bạn làm lộn xộn không gian tên toàn cục với quá trình' operator << 'quá tải. Phương thức này chứa toán tử quá tải trong định nghĩa 'A', và trình biên dịch vẫn sẽ tìm và khớp nó. – luk32

+1

Bạn không làm lộn xộn không gian tên chung với bất kỳ thứ gì trong cả hai trường hợp. Bạn định nghĩa toán tử << trong cùng một không gian tên như lớp và nó được tìm thấy thông qua ADL. –

0

Chỉ cần thêm mục này để giải trí. Trong trường hợp bạn tình cờ có nhiều hơn một phương pháp mà bản in/bãi trên lớp khác nhau:

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

đầu ra mong đợi:

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

Bạn có thể thêm một số nhận xét vào mã này để giải thích hai loại được phân cách bằng dấu phẩy trong 'decltype' và tất cả các công nghệ cao khác không? – AlwaysLearning

+0

chắc chắn, hạnh phúc. –

+0

@AlwaysLearning done. tận hưởng chuyến đi :-) –

Các vấn đề liên quan