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);
}
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.