C++ conditional execution depending on the type

Viewed 111

I have a template function that should execute different code, depends on the type. The simplified function looks like this:

template<typename T>
std::string test(T value)
{
    std::string v;
    if(std::is_arithmetic<T>())
    {
        v = std::to_string(value);
    }
    else
    {
       v = std::string(value);
    }
    return v;
}

Usage:

test("Hello");
test(123);

But I get this error:

In instantiation of void test(T) [with T = const char*]:
error: no matching function for call to to_string(const char*)
note: candidate: std::string std::__cxx11::to_string(int) <near match>
to_string(int __val)

and the same for the following:

to_string(unsigned __val)
to_string(long __val)
to_string(unsigned long __val)

Ok, I understand that in case of, for example const char * the compilation will fail since there is no std::to_string(const char *). but how can I do the code works? Just have to note that in my real code I limit to c++11.

4 Answers

You now get a taste of why if constexpr was added to the language. If you need to perform some type-dependent operation as part of your larger algorithm, then in pre-C++17 the way to do that is typically via tag-dispatch

namespace detail {
    template<typename T>
    std::string stringify(T value, std::true_type) {
        return std::to_string(value);
    }
    template<typename T>
    std::string stringify(T value, std::false_type) {
        return std::string(value);
    }
}

template<typename T>
std::string test(T value)
{
    std::string v;
    v = detail::stringify(value, std::is_arithmetic<T>());
    return v;
}

This is dispatching on two conditions, but the technique can be expanded to multiple overloads, depending on how you construct your tag types. A common example in the standard is iterator categories.

You can apply overloading with SFINAE. E.g.

// for arithmetic types
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type test(T value)
{
    std::string v;
    v = std::to_string(value);
    return v;
}

// for non-arithmetic types
template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value, std::string>::type test(T value)
{
    std::string v;
    v = std::string(value);
    return v;
}

The problem appears to be the part where it checks if it's an integer or not:

// essentially the center of the code
    if (std::is_arithmetic<T>())
    {
        v = std::to_string(value);
    }
    else
    {
       v = std::string(value);
    }

If you're using <= C++17, you could easily use if constexpr(), but you're not.

I would also suggest using SFINAE or tag-disspatch

If you want something available in C++11 and closer to your original attempt, you can use conditional_t with helper functors (one for arithmetic types and one for not)

namespace helper
{
template<typename T>
struct ARITHMETIC
{
    std::string operator()(T x)
    {
        return std::to_string(x);
    }
};

template<typename T>
struct NON_ARITHMETIC
{
    std::string operator()(T x)
    {
        return std::string(x);
    }
};
}

template <class T>
std::string test(T value)
{
    typename std::conditional<
        std::is_arithmetic<T>::value,
        helper::ARITHMETIC<T>,
        helper::NON_ARITHMETIC<T>>::type convert;

    return convert(value);
};

int main()
{
    std::cout << test("Hello") << std::endl;
    std::cout << test(123) << std::endl;
}
Related