Home > database >  Generic function template deduction over existing function overloads
Generic function template deduction over existing function overloads

Time:08-19

I'm writing an extensible library where it has become convenient to overload STL's to_string() for custom types. For that I've designed a generic overload template that throws an exception if not specialized:

namespace std {
// ...
template < typename T >
inline std::string to_string(const T& in, const std::string& separator = ",") {
    throw std::runtime_error("invalid call to "   std::string(__func__)   "(): missing template specialization for type "   typeid(T).name());
}

}  // namespace std

This is useful mainly because the description will provide a clear explanation on the issue and how to solve it, and avoids having to use polymorphism to implement derived implementations (the function is only marginally/optionally required for certain applications such as serialization, I/O, etc.).

However, the issue with this approach is that the overload template will be deduced even with types where <string> already provides an overload for.

My question is if is there a way to force the non-template overload to be used only when there is no non-template definition available?

CodePudding user response:

I recommend that you do not generate a runtime exception for something that should be a compilation failure.

It could look like this:

#include <string>
#include <type_traits>

namespace extra {
template <class T>
std::string to_string(const T& in) {
    if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(in); 
    } else {
        // using !is_same_v<T,T> to make it dependant on T (and always `false`)
        static_assert(!std::is_same_v<T,T>, "T needs extra::to_string overload");
        return {}; // will never happen
    }
}
}  // namespace extra

... and then you don't need to check if it's an arithmetic type at the call site:

template <class T>
void func(const T& arg) {
    std::cout << extra::to_string(arg);
}

Demo

CodePudding user response:

I ended up declaring to_string on a different namespace, and made use of type traits to delegate basic types towards STL's std::to_string:

namespace extra {

template < typename T >
struct invalid : std::false_type { /* ... */ };

template < typename T >
inline std::string to_string(const T& in) {
    // static_assert(invalid< iT >::value, "Invalid call to extra::to_string(): missing template specialization for required type!");   // never compiles
    throw std::runtime_error("Invalid call to extra::_to_string(): missing template specialization for required types["   std::string(typeid(T).name())   "]!");
}

}  // namespace extra


template < typename T >
void func(const T& arg) {
    // ...
    if constexpr (std::is_arithmetic< T >()) {
        std::cout << std::to_string(arg);
    } else {
        std::cout << extra::to_string(arg);
    }
    // ...
}

Although I am still trying to figure out how to proper write the static assertion in order to generate the error during compilation, at this stage this behaves how I needed it to.

  • Related