3

I would like to write a template function to convert various data types into strings using template specialization. Something like :

template< typename T >
string ToString( T value )
{
    return "?";
}

enum SomeEnum
{
    E_FOO,
    E_BAR
};

template<>
string ToString< SomeEnum >( SomeEnum value )
{
    switch( value )
    {
    case E_FOO: return "E_FOO";
    case E_BAR: return "E_BAR";
    }
}

Is it a good or bad practice to do somthing like that, and why ? Thank you.

Virus721
  • 8,061
  • 12
  • 67
  • 123
  • @BartoszKP How is it opinion-based ? – Virus721 Feb 01 '16 at 14:29
  • 1
    Function overloading may work better in this case - it will detect that you do not have overloads for certain types at compile time – Slava Feb 01 '16 at 14:29
  • 2
    Do you know about [`std::to_string`](http://en.cppreference.com/w/cpp/string/basic_string/to_string)? – NathanOliver Feb 01 '16 at 14:29
  • @Slava Thanks for your comment. What about types that can have implicit converions like int to float ? If I don't have an overload for int, but do have an overload for float, than using an int will call the float method, while it would call the "default" method reutrning "?" with the template, right ? – Virus721 Feb 01 '16 at 14:31
  • There's [this](http://stackoverflow.com/questions/201593/is-there-a-simple-way-to-convert-c-enum-to-string#201792) and [this](http://stackoverflow.com/questions/207976/how-to-easily-map-c-enums-to-strings). – LogicStuff Feb 01 '16 at 14:31
  • @Virus721 Yes, overloads may result in implicit type conversions in order to be able to select a function. If you want to avoid this you probably have to make sure that there's an overloaded version for all types (ie use templates). – skyking Feb 01 '16 at 14:34
  • @Virus721 yes it is possible for implicit types and easy to fix, on another side for other types that missing specializations I would prefer to see compiler diagnostics rather than `?` at runtime, which is more difficult to fix. – Slava Feb 01 '16 at 14:36
  • 2
    @Virus721 "good practices" are commonly accepted on a very general level. In case of specific concerns it always boils down to individual preferences vs very individual circumstances. For example - in some projects extensive usage of templates can be discouraged, in others encouraged etc. – BartoszKP Feb 01 '16 at 14:44
  • For programming tips and advice, you could take a look at codereview.se – edmz Feb 01 '16 at 15:26

2 Answers2

3

Use of template function specialization is rarely a good idea. It has awkward syntax, does not behave like people intuitively expect it to behave, does not allow for partial specialization, and is only best in rare corner cases.

There are better ways to do this.

Rather, use ADL (argument dependent lookup) and overloads. Consider using the name to_string if you want to use the std::to_string functions.

If you want, you can wrap this ADL in a way that lets users rely on it without having to do any work.

namespace my_stuff {
  namespace details {
    template<class...Ts> std::string ToString(Ts const&...) = delete;

    // put support for basic and `std` types in this namespace:
    std::string ToString(int t) { /* code */ }
    template<class T, class A>
    std::string ToString( std::vector<T,A> const & v) { /* code */ }

    template<class T> std::string ToString_impl(T const& t) {
      return ToString(t);
    }
  }

  template<class T> std::string ToString(T const& t) {
    return details::ToString_impl(t);
  }
}

Next, in the namespace of a type Foo, put ToString(Foo const& foo). It will automatically be found by a call to my_stuff::ToString(foo), even if Foo is not in the namespace my_stuff.

We put std and basic types in my_stuff::details (or whatever you want to call it) because introducing your own functions into namespace std is illegal, and because you cannot use ADL on basic types in the root namespace.

The trick here is ADL. By invoking ToString without an explicit namespace (in details::ToString_impl), the compiler looks for ToString both in the current namespace, and in the namespace of its arguments.

The default =deleted ToString is optional (you can remove it and I think it will still work), and is low priority (as it uses a variardic pack).

If you define ToString in the namespace of a type foo:

namespace SomeNamespace {
  struct Foo {};
  std::string ToString( Foo const& ) {
    return "This is a foo";
  }
}

a call to my_stuff::ToString(foo) will find it.

live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks for your answer. Though I don't understand the ToString_impl function. Is it recursive ? – Virus721 Feb 01 '16 at 14:51
  • why 3 levels of namespaces required? – Slava Feb 01 '16 at 14:51
  • @Slava The first, non-etails one, so someone outside can name `SomeNamespace::ToString` and have it magically work. It then bounces to an impl function, where I can `using Namespace3::toString`. I could make `detailsA` and `detailsB` the same namespace. The details needs to be in a different namespace. I'll reduce it to two, one sec. – Yakk - Adam Nevraumont Feb 01 '16 at 14:57
  • @Virus721 Sorry, a typo -- it was supposed to call `details::ToString` in an ADL-enabled context. Fixed now. – Yakk - Adam Nevraumont Feb 01 '16 at 14:57
  • (praising) nice explanation with a great example. Do you also use this approach for to-string in real world? – javaLover Jun 04 '17 at 08:09
  • 1
    @java I use it for other purposes, but not `ToString`, because I find that a generic "to string" function is rarely required in my code. Serialization, iteration both can use this technique however. – Yakk - Adam Nevraumont Jun 04 '17 at 09:25
1

I think more common is the overloading of the stream operators, so that you directly can put your data into a stream like:

struct MyType{
  int a;
};
std::ostream& operator <<(std::ostream& stream, const MyType& mt) {
  stream << "MyType("<<mt.a<<")";
  return stream;
}

MyType mt = {42};
std:cout << myData<< std::endl;
vlad_tepesch
  • 6,681
  • 1
  • 38
  • 80
  • 2
    Converting to a string does not always imply using streams. – BartoszKP Feb 01 '16 at 14:45
  • surely it does not. but its the stl approach. if you require a string in the end, then you use a stringstream. – vlad_tepesch Feb 01 '16 at 15:23
  • Not sure why anyone should care whether it's "stl approach" or whatever :) It's still solving a different problem and converting its solution, to do, what you wanted to do vs just solving the problem you have in the first place. – BartoszKP Feb 01 '16 at 15:38
  • @BartoszKP the problem with this question was that there is no problem. He was asking if his idea is good practice or not and i answered from a STL's perspective of view. – vlad_tepesch Feb 01 '16 at 16:24
  • Exactly - "there is no problem". And that is because the question is offtopic, so there is no point answering in first place. And, the OP was asking if this idea is a good practice to "convert into strings", not to be able to use streams. – BartoszKP Feb 01 '16 at 16:37