I believe, the most elegant solution is:
#include <string>
template <typename T>
typename std::enable_if<std::is_constructible<std::string, T>::value, std::string>::type
stringify(T&& value) {
return std::string(std::forward<T>(value)); // take advantage of perfect forwarding
}
template <typename T>
typename std::enable_if<!std::is_constructible<std::string, T>::value, std::string>::type
stringify(T&& value) {
using std::to_string; // take advantage of ADL (argument-dependent lookup)
return to_string(std::forward<T>(value)); // take advantage of perfect forwarding
}
Here, if we can construct std::string
using T
(we check it with help of std::is_constructible<std::string, T>
), then we do it, otherwise we use to_string
.
Of course, in C++14 you can replace typename std::enable_if<...>::type
with much shorter std::enable_if_t<...>
. An example is in the shorter version of the code, right below.
The following is a shorter version, but it's a bit less efficient, because it needs one extra move of std::string
(but if we do just a copy instead, it's even less efficient):
#include <string>
std::string stringify(std::string s) { // use implicit conversion to std::string
return std::move(s); // take advantage of move semantics
}
template <typename T>
std::enable_if_t<!std::is_convertible<T, std::string>::value, std::string>
stringify(T&& value) {
using std::to_string; // take advantage of ADL (argument-dependent lookup)
return to_string(std::forward<T>(value)); // take advantage of perfect forwarding
}
This version uses implicit conversion to std::string
then possible, and uses to_string
otherwise. Notice the usage of std::move
to take advantage of C++11 move semantics.
Here is why my solution is better than the currently most voted solution by @cerkiewny:
It have much wider applicability, because, thanks to ADL, it is also
defined for any type for which conversion using function to_string
is defined (not only std::
version of it), see the example usage below.
Whereas the solution by @cerkiewny only works for the fundamental
types and for the types from which std::string is constructible.
Of course, in his case it is possible to add extra overloads of
stringify
for other types, but it is a much less solid solution if
compared to adding new ADL versions of to_string
. And chances are
height, that ADL-compatible to_string
is already defined in a third party library for a
type we want to use. In this case, with my code you don't need to write any additional code at all to make stringify
work.
It is more efficient,
because it takes advantage of C++11 perfect forwarding (by using universal references (T&&
) and std::forward
).
Example usage:
#include <string>
namespace Geom {
class Point {
public:
Point(int x, int y) : x(x), y(y) {}
// This function is ADL-compatible and not only 'stringify' can benefit from it.
friend std::string to_string(const Point& p) {
return '(' + std::to_string(p.x) + ", " + std::to_string(p.y) + ')';
}
private:
int x;
int y;
};
}
#include <iostream>
#include "stringify.h" // inclusion of the code located at the top of this answer
int main() {
double d = 1.2;
std::cout << stringify(d) << std::endl; // outputs "1.200000"
char s[] = "Hello, World!";
std::cout << stringify(s) << std::endl; // outputs "Hello, World!"
Geom::Point p(1, 2);
std::cout << stringify(p) << std::endl; // outputs "(1, 2)"
}
Alternative, but not recommended approach
I also considered just overloading to_string
:
template <typename T>
typename std::enable_if<std::is_constructible<std::string, T>::value, std::string>::type
to_string(T&& value) {
return std::string(std::forward<T>(value)); // take advantage of perfect forwarding
}
And a shorter version using implicit conversion to std::string
:
std::string to_string(std::string s) { // use implicit conversion to std::string
return std::move(s); // take advantage of move semantics
}
But these have serious limitations: we need to remember to write to_string
instead of std::to_string
everywhere where we want to use it; also it is incompatible with the most common ADL usage pattern:
int main() {
std::string a = std::to_string("Hello World!"); // error
using std::to_string; // ADL
std::string b = to_string("Hello World!"); // error
}
And it's most probable, there are other problems connected with this approach.