22

Right now I use the following piece of code to dummily convert basic types (int, long, char[], this kind of stuff) to std::string for further processing:

template<class T>
constexpr std::string stringify(const T& t)
{
    std::stringstream ss;
    ss << t;
    return ss.str();
}

however I don't like the fact that it depends on std::stringstream. I tried using std::to_string (from C++11's repertoire) however it chokes on char[] variables.

Is there a simple way offering an elegant solution for this problem?

Ferenc Deak
  • 34,348
  • 17
  • 99
  • 167
  • 11
    Had similar problem, ended up specialising the template in case of literals and char[]... Hope someone knows easier solution. – cerkiewny May 11 '15 at 11:42
  • @cerkiewny You should post that as an answer. – vsoftco May 11 '15 at 12:04
  • What exactly makes you dislike the `std::stringstream` dependency? Because I've done with a [SSTR() macro](http://stackoverflow.com/a/5590404/60281) since well before `std::to_string` came about, always liked its ability to daisy-chain multiple `<<`, but can't really post that as an answer because you said "no stringstream"... – DevSolar May 11 '15 at 15:06
  • 1
    [Related](http://stackoverflow.com/q/23437778/2567683). Therein the following methods are mentioned : stringstream, to_string, boost::spirit::karma, boost::lexical_cast – Nikos Athanasiou May 11 '15 at 15:36

6 Answers6

10

As far as I know the only way of doing this is by specialising the template by the parameter type with SFINAE.

You need to include the type_traits.

So instead of your code use something like this:

template<class T>
 typename std::enable_if<std::is_fundamental<T>::value, std::string>::type stringify(const T& t)
  {
    return std::to_string(t);
  }

template<class T>
  typename std::enable_if<!std::is_fundamental<T>::value, std::string>::type  stringify(const T& t)
  {
    return std::string(t);
  }

this test works for me:

int main()
{
  std::cout << stringify(3.0f);
  std::cout << stringify("Asdf");
}

Important note: the char arrays passed to this function need to be null terminated!

As noted in the comments by yakk you can get rid of the null termination with:

template<size_t N> std::string stringify( char(const& s)[N] ) { 
    if (N && !s[N-1]) return {s, s+N-1};
    else return {s, s+N}; 
}
cerkiewny
  • 2,761
  • 18
  • 36
  • Nice, but won't work for classes that implement `operator<<`. – edmz May 11 '15 at 12:34
  • 2
    @black My answer will handle that. You just need to tweak the `enable_if` a bit and add in the `ostringstream`. – Jonathan Mee May 11 '15 at 12:35
  • 1
    Testing whether or not `std::to_string(t)` is well-formed as the SFINAE-condition might be a better check. E.g. `template auto stringify(T&& t) -> decltype(std::to_string(std::forward(t))) { return std::to_string(std::forward(t)); }` – dyp May 11 '15 at 14:48
  • @dyp That's a really good solution if you answer with that I'll give you a +1. Otherwise I'm gonna steal that answer into my code. – Jonathan Mee May 11 '15 at 16:08
  • 1
    `template std::string stringify( char(const& s)[N] ) { if (N && !s[N-1]) return {s, s+N-1}; else return {s, s+N}; }` gets rid of null terminated requirement. – Yakk - Adam Nevraumont May 11 '15 at 19:30
  • @Yakk Wow, I didn't know that you could do return constructors without declaring the type! – Jonathan Mee May 13 '15 at 11:13
  • 1
    @cerkiewny If you have c++14 you can use `enable_if_t<`...`>` instead of `template enable_it<`...`>::type`. – Jonathan Mee May 13 '15 at 11:15
  • 2
    `constexpr` is meaningless in this answer because `std::string` is not a [literal type](http://en.cppreference.com/w/cpp/concept/LiteralType). And here are more serious flaws. See [my answer](http://stackoverflow.com/a/30276624/2266855) for details. – dened May 17 '15 at 09:35
9

Is there a simple way offering an elegant solution for this problem?

Since nobody proposed it, consider using boost::lexical_cast.

This integrates seamlessly with anything that implements std::ostream<< operator and can be extended for custom types.

utnapistim
  • 26,809
  • 3
  • 46
  • 82
  • I actually thought about it but since he is not happy with STL dependancies with string stream i thought the boost::lexical_cast wont be the way either... but surely it is nice alternative. – cerkiewny May 11 '15 at 13:34
  • 3
    I have found this before: people tend to think that having a dependency to an object, somehow makes the code monolythic/heavy on resources/slow/ugly. – utnapistim May 11 '15 at 14:04
  • 1
    boost::lexical_cast, at least in my experience, was so slow as to make it unusable. – Ami Tavory May 12 '15 at 11:49
5

I'd recommend using enable_if_t and if you're going to take in any single character variables you specialize those:

template<typename T>
enable_if_t<is_arithmetic<T>::value, string> stringify(T t){
    return to_string(t);
}

template<typename T>
enable_if_t<!is_arithmetic<T>::value, string> stringify(T t){
    return static_cast<ostringstream&>(ostringstream() << t).str();
}

template<>
string stringify<char>(char t){
    return string(1, t);
}

Here I'm just specializing char. If you need to specialize wchar, char16, or char32 you'll need to do that as well.

Anyway for non-arithmetic types these overloads will default to using ostringstream which is good cause if you've overloaded the extraction operator for one of your classes this will handle it.

For arithmetic types this will use to_string, with the exception of char and anything else you overload, and those can directly create a string.

Edit:

Dyp suggested using whether to_string accepts an argument of T::type as my enable_if_t condition.

The simplest solution is only available to you if you have access to is_detected in #include <experimental/type_traits>. If you do just define:

template<typename T>
using to_string_t = decltype(to_string(declval<T>()));

Then you can set your code up as:

template<typename T>
decltype(to_string(T{})) stringify(T t){
    return to_string(t);
}

template<typename T>
enable_if_t<!experimental::is_detected<to_string_t, T>::value, string> (T t){
    return static_cast<ostringstream&>(ostringstream() << t).str();
}

template<>
string stringify<char>(char t){
    return string(1, t);
}

I asked this question to figure out how to use to_string as my condition. If you don't have access to is_detected I'd highly recommend reading through some of the answers cause they are phenomenal: Metaprograming: Failure of Function Definition Defines a Separate Function

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • Feel free to "steal". No need to add yet another answer based on SFINAE. – dyp May 11 '15 at 17:38
  • @dyp This seems like a good idea but when I go to implement it, I cannot figure out how to code the diametric opposite. How can I say: "Return string if `to_string` is not defined?" – Jonathan Mee May 11 '15 at 18:35
  • You can either turn the trailing-return-type into a trait class, or add a dummy parameter for ordering of overloads. The latter: `template string stringify(T&& t) { return stringify(forward(t), 0); } template auto stringify(T&& t, int) -> decltype(to_string(forward(t))); template string stringify(T&& t, ...);` A more advanced way using inheritance can be found [in this blog post](http://flamingdangerzone.com/cxx11/overload-ranking/). – dyp May 11 '15 at 20:35
  • @dyp Seems like there's got to be an easier way to accomplish this. I added a question [here](http://stackoverflow.com/q/30189926/2642059) which you may want to weigh in on. – Jonathan Mee May 12 '15 at 12:08
3

The simplest solution is to overload for the types you want:

using std::to_string;

template<size_t Size>
std::string to_string(const char (&arr)[Size])
{
    return std::string(arr, Size - 1);
}

since to_string isn't a template you can't specialize it, but fortunately this is easier.

The code assumes the array is null terminated, but is still safe if it is not.

You may also want to put the using line inside the functions that call to_string if you have strong feelings about where using belongs.

This also has the benefit that if you pass it a non-null-terminated string somehow, it does not have UB as the one argument std::string constructor does.

Dan
  • 12,409
  • 3
  • 50
  • 87
  • Whether or not you want `Size - 1` depends on whether or not it is NUL terminated. So, your code can check for that. – jxh May 11 '15 at 17:49
  • I was going to do that (ie, pick depending on `arr[Size-1]`), but then if the string contains nulls, one of which happens to be at the end, it will chop off the last one and possibly cause problems. – Dan May 11 '15 at 20:43
  • I'm confused. If I wanted to store a single binary byte containing `'\0'`, your code will not store it anyway. If I wanted to store a single binary byte containing `'\a'`, your code will not store that either. – jxh May 11 '15 at 21:03
  • This solution is not perfect. See my answer for some details. – dened May 16 '15 at 14:05
3

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.

Community
  • 1
  • 1
dened
  • 4,253
  • 18
  • 34
  • I like the usage of the is_constructible, I wasn't aware of its existence. – cerkiewny May 18 '15 at 10:54
  • With clang, you get better error messages with `std::enable_if` than with `std::enable_if_t`. – Paul Fultz II May 28 '15 at 19:47
  • 1
    It may not be obvious to many why ADL is important here, something along the lines of but obviously not the same as [this](http://stackoverflow.com/questions/17136497/is-overriding-stdto-string-for-user-defined-enums-proper-way-to-privide-to-str#comment24801066_17136607) added to your answer would be an improvement. – Shafik Yaghmour Jun 01 '15 at 12:24
2

Although the the question is not of a gimme the code kind, since I already have a solution implemented I thought of sharing it:

template <class... Tail>
inline auto buildString(std::string const &head, Tail const &... tail)
    -> std::string;

template <class... Tail>
inline auto buildString(char const *head, Tail const &... tail) -> std::string;

template <class... Tail>
inline auto buildString(char *head, Tail const &... tail) -> std::string;

template <class Head, class... Tail>
inline auto buildString(Head const &head, Tail const &... tail) -> std::string;

inline auto buildString() -> std::string { return {}; }

template <class... Tail>
inline auto buildString(std::string const &head, Tail const &... tail)
    -> std::string {
  return head + buildString(tail...);
}
template <class... Tail>
inline auto buildString(char const *head, Tail const &... tail) -> std::string {
  return std::string{head} + buildString(tail...);
}
template <class... Tail>
inline auto buildString(char *head, Tail const &... tail) -> std::string {
  return std::string{head} + buildString(tail...);
}
template <class Head, class... Tail>
inline auto buildString(Head const &head, Tail const &... tail) -> std::string {
  return std::to_string(head) + buildString(tail...);
}

Usage:

auto gimmeTheString(std::string const &str) -> void {
  cout << str << endl;
}

int main() {

  std::string cpp_string{"This c++ string"};
  char const c_string[] = "this c string";

  gimmeTheString(buildString("I have some strings: ", cpp_string, " and ",
                             c_string, " and some number ", 24));
  return 0;
}
bolov
  • 72,283
  • 15
  • 145
  • 224