4

I have class:

template <typename val_t>
class tracer_t : public base_tracer_t<val_t> {
    std::vector<std::string> m_trace;
public:
    virtual void push_fact(val_t fact) override {
        std::string str = "+ fact: " + to_string(fact);
        m_trace.push_back(std::move(str));
    }

    virtual void push_rule(const std::string &id, val_t val, bool tg) override {
        std::string str = "+ ";
        if (tg) { str += "target: "; }
        else { str += "rule: "; }
        str += id + " -> " + to_string(val);
        m_trace.push_back(std::move(str));
    }

    virtual void print() override {
        std::cout << "Stack trace: " << std::endl;
        for (auto it = m_trace.begin(); it != m_trace.end(); ++it) {
            std::cout << (*it) << std::endl;
        }
    }
private:
    std::string to_string(val_t val) {
        if (std::is_same<val_t, std::string>::value) {
            return (std::string)val;
        }
        return std::to_string(val);
    }
};

The problem is that it doesn't compile if val_t is std::string because of:

tracer.hpp:49: error: no matching function for call to ‘to_string(std::__cxx11::basic_string<char>&)’
         return std::to_string(val);
                ~~~~~~~~~~~~~~^~~~~

But I can't get how to resolve it. I tried to check type manually but error is in compile time,so it didn't help

Denis Sologub
  • 7,277
  • 11
  • 56
  • 123

2 Answers2

7

If you don't want to specialize the entire class for std::string you could use std::enable_if or if constexpr(c++17)

auto to_string(val_t val)
    -> typename std::enable_if<std::is_same<val_t, std::string>::value, std::string>::type 
{
    return static_cast<std::string>(val);
}

auto to_string(val_t val)
    -> typename std::enable_if<!std::is_same<val_t, std::string>::value, std::string>::type 
{
    return std::to_string(val);
}

Or the more modern approach, with if constexpr

auto to_string(val_t val)
{
    if constexpr (std::is_same<val_t, std::string>::value)
    {
        return static_cast<std::string>(val);
    }
    else
    {
        return std::to_string(val);
    }
}
Petok Lorand
  • 955
  • 6
  • 15
5

You could just provide a new overload for to_string

std::string to_string(const std::string& s) { return s; }

You could put the code above inside the class, as a private method, or inside a suitable namespace, so to avoid possible clashes when, say, somebody uses your code and wishes to write her/his own overload of to_string.

EDIT: As noted in the comments below, you cannot put such an overload in the std namespace, as a new declaration of std::to_string is forbidden, see Extending the namespace std.

EDIT: If you need to possibly call std::to_string, you may need to add an additional to_string template function to your code as

template <typename T>
typename std::enable_if<!std::is_convertible<T, std::string>::value, std::string>::type to_string(T r) const { return std::to_string(r); }

(Do not forget #include <type_traits> for that).

This is because, even if you import the standard library std::to_string by using namespace std, the member function to_string will have the priority. See the discussion: C++: Why member function has priority over global function. Here you can see a minimal example.

francesco
  • 7,189
  • 7
  • 22
  • 49
  • 2
    Overloading `std::to_string` is forbidden! At best you may specialize template functions in namespace `std`, but even that will go away in C++20. – Max Langhof Jan 21 '19 at 09:48
  • @MaxLanghof Even if it's a member function? – jrok Jan 21 '19 at 09:49
  • 2
    @jrok That's fine, but then it's not `std::to_string` you're overloading. – Max Langhof Jan 21 '19 at 09:49
  • 1
    @MaxLanghof As is the case here, no? – jrok Jan 21 '19 at 09:50
  • 1
    @MaxLanghof The answer clearly made a mistake and was intended to refer to a `to_string` function outside of `std`. – Konrad Rudolph Jan 21 '19 at 09:50
  • @KonradRudolph Thanks for the correction! I have added an explanation. – francesco Jan 21 '19 at 10:10
  • I got `error: call of overloaded ‘to_string(std::__cxx11::basic_string&)’ is ambiguous`. Can you put sample, pls? – Denis Sologub Jan 21 '19 at 10:11
  • @MaxLanghof Thanks a lot for the comment. I have added an explanation with a relevant link to the reference. – francesco Jan 21 '19 at 10:12
  • @Шах See my addition: You cannot directly overload ```std::to_string```. – francesco Jan 21 '19 at 10:13
  • @francesco, but it says I can extend a standard library in C++ 20 only, I need C++ 11 – Denis Sologub Jan 21 '19 at 10:14
  • 1
    @Шах not really. It says you can, in c++20, add a specialization of a *class template*. ```std::to_string``` is neither a class, nor a template function. This is why you have to add the code above inside the class, or inside a suitable namespace, not ```std```. – francesco Jan 21 '19 at 10:19
  • @francesco, I tried to add inside class to specify `to_string` realization for `const std::string &` type but it says `call of overloaded ‘to_string(std::__cxx11::basic_string&)’ is ambiguous`. What am I doing wrong? Sorry, I'm a noobie in C++. – Denis Sologub Jan 21 '19 at 10:22