Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to simplify these function template specializations?

I am working on some legacy code that has the following template defined in a header file:

template<typename T>
std::string convertToString(const T& t);

In the same header file, there are specializations for some user-defined types. In the .cpp file, there are specializations for the types int, unsigned short and unsigned long as follows:

template<>
std::string convertToString<unsigned short>(const unsigned short& s) {
    ... some implementation ...
}

template<>
std::string convertToString<int>(const int & s) {
    ... some implementation ...
}

template<>
std::string convertToString<unsigned long>(const unsigned long& value) {
    ... some implementation ...
}

The implementations don't make use of C++11's std::to_string function. I would like to simplify this code so that for all types that std::to_string supports, namely

int
long
long long
unsigned
unsigned long
unsigned long long
float
double
long double

the template specialization uses the simple implementation (e.g. for int):

template<>
std::string convertToString(const int& n) { return std::to_string(n); }

I could of course write down all template specializations for all the above mentioned types, but that doesn't seem like a nice way to do it. My current best solution to have these template specializations for all types that std::to_string takes is to add the following to my header file:

template<typename T>
std::string convertToString(const T& t);

template <typename T,
          typename = typename std::enable_if<std::is_arithmetic<T>::value>::type>
std::string convertToString(const T& t)
{
    return std::to_string(t);
}

I think this is close to a solution, but not yet as I get the following ambiguity error:

error: call of overloaded 'convertToString(long unsigned int&)' is ambiguous

referring me to the above two template functions as possible candidates. I don't quite understand why the first one is also a possible candidate if the second one exists...

How can I add template specializations for all the types that std::to_string supports, and also still allow for user-defined types T in the convertToString function template? Solution should be restricted to C++11 for now...

UPDATE: adding a minimal working example below (code must be C++11):

So I want to replace the following code by something simpler:

#include <iostream>
#include <string>

struct A {};

template<typename T>
std::string convertToString(const T& t);

template<>
std::string convertToString(const int& n) { return std::to_string(n); }

template<>
std::string convertToString(const long& n) { return std::to_string(n); }

template<>
std::string convertToString(const long long& n) { return std::to_string(n); }

template<>
std::string convertToString(const unsigned& n) { return std::to_string(n); }

template<>
std::string convertToString(const unsigned long& n) { return std::to_string(n); }

template<>
std::string convertToString(const unsigned long long& n) { return std::to_string(n); }

template<>
std::string convertToString(const float& n) { return std::to_string(n); }

template<>
std::string convertToString(const double& n) { return std::to_string(n); }

template<>
std::string convertToString(const long double& n) { return std::to_string(n); }

template<>
std::string convertToString<A>(const A& a) { return std::string("foo"); }

int main()
{
    std::cout << convertToString(1) << std::endl;
    std::cout << convertToString(2L) << std::endl;
    std::cout << convertToString(3LL) << std::endl;
    std::cout << convertToString(4U) << std::endl;
    std::cout << convertToString(5UL) << std::endl;
    std::cout << convertToString(6ULL) << std::endl;
    std::cout << convertToString(7.0f) << std::endl;
    std::cout << convertToString(8.0) << std::endl;
    std::cout << convertToString(9.0L) << std::endl;
    std::cout << convertToString(A()) << std::endl;
}

I tried the folowing which gives me compiler errors:

#include <iostream>
#include <string>
#include <type_traits>

struct A {};

template<typename T,
         typename = typename std::enable_if<!std::is_arithmetic<T>::value>::type>
    std::string convertToString(const T& t);

template <typename T,
          typename = typename std::enable_if<std::is_arithmetic<T>::value>::type>
    std::string convertToString(const T& t)
{
    return std::to_string(t);
}

template<>
std::string convertToString<A>(const A& a) { return std::string("foo"); }

int main()
{
    std::cout << convertToString(A()) << std::endl;
    std::cout << convertToString(1) << std::endl;
    std::cout << convertToString(2L) << std::endl;
    std::cout << convertToString(3LL) << std::endl;
    std::cout << convertToString(4U) << std::endl;
    std::cout << convertToString(5UL) << std::endl;
    std::cout << convertToString(6ULL) << std::endl;
    std::cout << convertToString(7.0f) << std::endl;
    std::cout << convertToString(8.0) << std::endl;
    std::cout << convertToString(9.0L) << std::endl;
}

If someone could explain me what's wrong with this, that would be nice.

like image 976
Bart Vandewoestyne Avatar asked Oct 29 '25 02:10

Bart Vandewoestyne


2 Answers

You could create a type trait that checks if std::to_string(t) is valid and use SFINAE to only allow for the overloads that uses std::to_string to be instantiated if it's valid.

Type trait example:

#include <type_traits>

#if __cplusplus >= 201703L
using std::void_t;
#else
template <class...>
using void_t = void; // borrowed from C++17
#endif

template <class T, class = void>
struct has_to_string : std::false_type {};

template <class T>
struct has_to_string<T, void_t<decltype(std::to_string(std::declval<T>()))>>
    : std::true_type {};

Usage example:

template <class T>
typename std::enable_if<has_to_string<T>::value, std::string>::type
convertToString(const T& t) {
    return std::to_string(t);
}
like image 149
Ted Lyngmo Avatar answered Oct 31 '25 17:10

Ted Lyngmo


You can write two overloads with disjunct conditions for the SFINAE:

// overload for T is not is_arithmetic
template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value,std::string>::type convertToString(const T& t);

// overload for T is arithmetic
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value,std::string>::type convertToString(const T& t)
{
    return std::to_string(t);
}
like image 36
463035818_is_not_a_number Avatar answered Oct 31 '25 15:10

463035818_is_not_a_number



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!