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
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
Tại sao không đơn giản là quá tải 'toán tử <<' bên trong 'A'? – luk32
@ 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