只是增加了這個樂趣。如果你碰巧有一個以上的方法在不同的類,打印/轉儲:
#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
你爲什麼不只是超載''<<代替自卸這些方法?實現起來更直觀,更簡單。 – erip
爲什麼不簡單地在'A'裏面重載'operator <<'? – luk32
@ luk32'operator <<'不能是成員函數,可以嗎?所以'dump'成員函數更方便,因爲它們可以直接訪問數據成員(不需要使用點運算符)。 – AlwaysLearning