4

As stated here std::string is not a template function but rather the standard choose to use function overloading to provide this function for different types. My question is why use overloading when template/specialisation seems to make more sense to me in this case? Consider that if the standard has defined something like this:

template <typename T>
std::string std::to_string(const T& v);

Then we can freely add specialisation for any type in our program to conform to this signature, thus C++ will have a uniform way to transform types into human-readable strings. Why not do this? What's the thinking behind the current design?

Edit 1:

The main critic I have for the current design is that adding an overload to std is not allowed so we can not write anything like std:to_string(object-that-is-of-user-defined-types) and has to fall back on defining a to_string() in their own namespace and remember where to use their version or the std version depends on the types they are dealing with... This sounds like a headache for me.

One thing I really liked about Python (or some other languages) is that you can make your own type work just like a native type by implementing some magic methods. I think what this question is fundamentally about is that why C++ decided to disallow people to implement std::to_string() for their own type and thus forbid us from conforming to the same interface everywhere.

For common things like hash or to_string(), isn't it better to have a single interface on language/stdlib level and then expect users to conform to that interface, rather than having multiple interfaces?

kesarling He-Him
  • 1,944
  • 3
  • 14
  • 39
Bob Fang
  • 6,963
  • 10
  • 39
  • 72
  • You can also add an overload if you wish. An overload will also work better with argument dependent lookup, since you can put the overload in the same namespace as the argument type. Plus I don't believe you are permitted to specialize templates in the std namespace. – john Jul 07 '20 at 08:05
  • @john I know we can add overload. My question is more why use overload rather than template/specialisation? What's the tradeoff? Is this just an arbitrary decision made by the people who proposed these function? – Bob Fang Jul 07 '20 at 08:06
  • Well I suggested a couple of reasons, see updated comment. – john Jul 07 '20 at 08:07
  • 2
    @john adding template specialization is one of exceptions where you are allowed to change `std` namespace – bartop Jul 07 '20 at 08:07
  • @bartop I didn't realise that but according to [this page](https://en.cppreference.com/w/cpp/language/extending_std) this will change in C++20 for function templates (like `to_string`). – john Jul 07 '20 at 08:10
  • 1
    Why do people believe every little thing in C++ should be templated? [Template Specialization VS Function Overloading](https://stackoverflow.com/a/7108123/1810087) – user1810087 Jul 07 '20 at 08:10
  • Overloading is in general preferred over template specialization for a few reasons. There are question on that here on SO if you search. – super Jul 07 '20 at 08:10
  • 2
    From a design perspective, it only makes sense to template a function if it is possible to specify the behaviour of that function for an unbounded number of arbitrary types. The standard can't sensibly do that for anything other than the types it does (basic numeric types). The set it *can* specify is finite, which means overloading only for those types is appropriate, not a template. – Peter Jul 07 '20 at 08:17
  • @john I find this unfortunate really. One thing I really liked about Python is that you can make your types behave like a native type by just implementing the magical methods. In my opinion, this change will close the door for C++ to do the same from C++ 20 onwards. – Bob Fang Jul 07 '20 at 08:24
  • @Peter `[…]The set it can specify is finite, which means overloading only for those types is appropriate, not a template[…]` that depends on what the idea behind `to_string` is. It could also be specified similar to `begin` and `end`, looking e.g. for a `to_string` member function. It is for sure arguable in which general way objects could be converted to a string, and that's might be the reason why it was done that way. – t.niese Jul 07 '20 at 08:25
  • 2
    you do not need to add an overload to `std`. It works for `swap` and it also works for other functions. You can put the overload anywhere as long as it is found by ADL, the answer explains that in detail. The downside that you see is not there – 463035818_is_not_an_ai Jul 07 '20 at 08:37
  • @t.niese - That's the point. Given an arbitrary type `Foo` which is not a typedef/alias for one of the basic numeric types, the standard can't specify how conversion to a `string` should go. That arbitrary type is not necessarily writable to an output stream, doesn't necessarily supply iterators, or has a `to_string()` member. The set of types for which the standard can specify a conversion to string is bounded, and the set that cannot be converted is unbounded - exactly the opposite of what makes sense for a template. – Peter Jul 07 '20 at 08:40
  • @Peter yes but also not every type is iterable or likely to be iterable, but still `std::begin` and `std::end` are template-based. There exists other languages - I don't want to say that it is a good idea - for which each object has a to value and to string member function. So the committee could also have gone that direction, but they just decided not do it. – t.niese Jul 07 '20 at 08:46
  • @t.niese for `begin` and `end` there are loads of types that can go with the same / very similar implementation. I don't see that advantage for `to_string` – 463035818_is_not_an_ai Jul 07 '20 at 08:47
  • @t.niese - The number of iterable types (like the standard containers, which are templated on the type of their elements, and arrays which have different types if their elements are different types) which `std::begin()` and `std::end()` templates AND overloads can act on - is unbounded. There is no such justification for `to_string()`. – Peter Jul 07 '20 at 12:02
  • @t.niese - Also, your portrayal that every object not having `to_value()` or `to_string()` capability in C++ as a committee decision is false. Core design constraints that *define* C++ as a language include getting close to the host platform, and "don't pay for what you don't use". The committee is *required* (except in very specific circumstances) to comply with those,and cannot simply vote in features that break those design constraints. If people need a language in which all objects have baked-in capability to have `to_value()` or `to_string()` they need to use a language other than C++ – Peter Jul 07 '20 at 12:14

2 Answers2

5

why C++ decided to disallow people to implement std::to_string for their own type

This is where ADL is useful. We already have the example of how to correctly do this with std::swap, which is successfully done in many codebases already:

template <typename T>
void swap_example(T & a, T & b) {
    using std::swap;
    swap(a, b);
}

This works if the namespace T is declared in has a compatible swap() function, without needing to overload std::swap. We can do the same thing with std::to_string:

template <typename T>
void to_string_example(T const & x) {
    using std::to_string;
    to_string(x);
}

This will likewise work if the namespace T is declared in has a to_string function that can accept a T const & argument. For example:

namespace example {
    class Foo;

    std::string to_string(Foo const &);
}

to_string_example(example::Foo{}) would find and use the corresponding example::to_string function.


remember where to use their version or the std version depends on the types they are dealing with... This sounds like a headache for me.

If this really is such a headache for you, you can hide the ADL behind a utility function in your project:

template <typename T>
std::string adl_to_string(T const & x) {
    using std::to_string;
    return to_string(x);
}

Now you can use adl_to_string(...) instead of std::to_string(...) everywhere and not have to think about it.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
2

This may sound a bit boring, but the purpose of std::to_string is to format as if you had used sprintf and as sprintf supports only a limited set of types this is also true for std::to_string. There is no need to make it a template.

As explained in detail in this answer that design does not have the restriction you think it has. You can still supply your own foo::to_string(foo::bar&) and in code that uses proper qualification of the name to enable ADL your overload will be called. For this it is not necessary to add an overload to std.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185