Pergunta

I'm a beginner C++ developer and I have a question about toString and ostream operator integration via templates. I have such code:

    struct MethodCheckerTypes{
        typedef unsigned char TrueType;
        typedef long FalseType;
    };
    template<typename T>struct HasToString{
        template<typename K>static typename MethodCheckerTypes::TrueType test(decltype(&K::toString));
        template<typename> static typename MethodCheckerTypes::FalseType test(...);
        static const bool value = sizeof(test<T>(0)) == sizeof(typename MethodCheckerTypes::TrueType);
        typedef decltype(test<T>(0)) ValueType;
    };

    template<typename T, typename K> struct IfDef{};
    template<> struct IfDef<typename MethodCheckerTypes::TrueType, ostream&>{
        typedef ostream& type;
    };

    class WithToString{
    public:
        string toString() const{
            return "hello";
        }
    };

    template<typename F, typename CharT, typename Traits> typename IfDef<typename HasToString<F>::ValueType, basic_ostream<CharT, Traits>&>::type operator<<(basic_ostream<CharT, Traits>& out, const F &ref){
        out<<ref.toString().c_str();
        return out;
    }
int main(int argc, char **argv){
    WithToString hasToString;
    cout<<hasToString<<endl;
    return 0;
}

The code has been compilled without errors, and the application executed successfuly. Is it good to use such an approach? I wanted to implement it without any help from boost.

Foi útil?

Solução

The approach to implementing operator<< by itself is normal. But using parts of the language you do not understand is bad practice (I do not believe that a beginner can write such code). You have two alternatives: to implement a toString member function or overload operator<<(std::ostream&, T). The latter approach enables you to use boost::lexical_cast to convert an object to a string. As for me, the latter approach is more C++ish, if you can do something without a member function it is better to do so.

Outras dicas

I took approach using @Begemoth solution overloading stream operator << and then adding a convenience "mixin" class "toString" method in cases where you want an immediate string, for example for debugging:

template<typename T>
class ToString
{
public:
    std::string toString() const
    {
        return convertToString(static_cast<const T&>(*this));
    }
};

template<typename T>
inline std::string convertToString(const T& value)
{
    std::stringstream s;
    s << value;
    return s.str();
}

You can then inherit from this class in cases you have ostream operator and it will give you a toString method on that class. If you are using boost then you can replace implementation of convertToString using lexical_cast:

template<typename T>
inline std::string convertToString(const T& value)
{
    return boost::lexical_cast<std::string>(value);
}

If your class is designed for inheritance and polymorphic access through pointers then above solution won't work as templates are tied at compile time, so a different approach is needed. The following mixin class can be used in place of "ToString" above where a virtual method is defined. A convenience macro is then provided to implement the virtual function in terms of the template function "convertToString". This must be added into the class body of each derived class as it needs to be overridden and the "this" pointer tied statically to the current type.

class ToStringVirtual
{
public:
    virtual ~ToStringVirtual() {}
    virtual std::string toString() const = 0;
};

#define IMPLEMENT_ToStringVirtual public: std::string toString() const override { return convertToString(*this); }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top