Generic implementation of operator<< function in terms of the dump member function

前端 未结 4 1850
时光说笑
时光说笑 2021-01-05 20:17

All my classes implement a dump member function, e.g.:

struct A {
    template 
    std::basic_ostream &
         


        
相关标签:
4条回答
  • 2021-01-05 20:47

    You could make an empty base class, say:

    struct HasDump {};
    

    And make HasDump a base of all your classes, that is:

    struct A : HasDump ( ...
    

    Then wrap your operator<< with std::enable_if and std::is_base_of so it only applies when HasDump is a base of T.

    (I haven't focused on C++ for a year or two so this advice might be a little rusty)

    0 讨论(0)
  • 2021-01-05 20:52
    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);
    }
    

    This overloads operator<< only for types that define an appropriate dump member.

    Edit: added static_assert for better messages.

    0 讨论(0)
  • 2021-01-05 21:01

    Generally this would be the advisable way, IMO:

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

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

    If you really want to have a dedicated dump method, you could define a base class "collecting" dumpable objects.

    0 讨论(0)
  • 2021-01-05 21:01

    Just added this for fun. In case you happen to have more than one method that prints/dumps on different classes:

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

    expected output:

    donkey: 5, horse, banana!?!5::printed
    
    0 讨论(0)
提交回复
热议问题