The problem: a library function offers several overloads, but depending on the implementation/compiler, some of the overloads are not available. How to check the existence of an overload? And how to provide a safe fallback?
In this article, I’ll show you a background “theory” and one case - std::from_chars
that exposes full support for numbers or only integer support (in GCC, Clang).
Intro - Function Overloads
Before we jump into a more complex problem, let’s start with something simpler. This will allow us to understand the final solution easily.
Imagine a library that provides a function Compute()
:
// lib V1:
void Compute(int in, int& out) { }
Later in the second version of the library, you’ll have a new overload.
// lib V2:
void Compute(int in, int& out) { }
void Compute(double in, double& out) { }
The problem is that you want to have support both int
and double
in your project no matter what’s the version of the library used. In a case, the library version doesn’t contain a necessary overload you can provide a custom alternative.
But how to check it effectively?
Using The Preprocessor
If you know the version of the library and you have all required defines, you can use preprocessor and create a following solution:
// provide custom overload for double if we use V1
#if LIB_VERSION == LIBV1
void Compute(double in, double& out) { /* custom code */ }
#endif
In the above code, you use defines and macros to provide a custom overload for the Compute()
function.
This might work, but what if you have another version of the library? With even more complex overloads. The #if
approach might quickly become a mess of preprocessor code. What if we could “detect” if a function has a given overload?
Templates to the Rescue - The Detection Pattern!
What we need is a way to ask the compiler:
// pseudocode:
if (overload Compute(double, double&) not exists) { }
While it’s not possible with macros and preprocessor, you can detect a function existence using templates.
The detection idiom might work in the following way for our Compute()
function:
template <typename T, typename = void>
struct is_compute_available : std::false_type {};
template <typename T>
struct is_compute_available<T,
std::void_t<decltype(Compute(std::declval<T>(),
std::declval<T&>())) >> : std::true_type {};
The above code creates a template structure is_compute_available
. By default, the structure derives from false_type
. But when you provide a T
for which Compute()
has an overload, then we “activate” the partial template specialisation that derives from true_type
.
The core part is void_t
magic that tries to check if the overload is available. If the whole expression is not valid, it’s SFINAEd, and the specialisation is gone. Otherwise, the template specialisation is, and the compiler will select it.
How does std::void_t
work?
std::void_t
is a relatively simple template that can help with SFINAE magic. It was added in C++17 and it’s implementation is surprisingly straightforward:
template< class... >
using void_t = void;
See more info at cppreference
The basic idea is that you can put many compile-time checks, and if something fails, then the whole expression is SFINAEd. This helper type is often used for detection pattern.
For our Compute()
check we use the following code:
template <typename T>
struct is_compute_available<T,
std::void_t<decltype(Compute(std::declval<T>(),
std::declval<T&>())) >> : std::true_type {};
The internal check uses:
decltype(Compute(std::declval<T>(), std::declval<T&>()))
What we do here is we’re trying to find the return type of a function overload that takes std::declval<T>()
and std::declval<T&>()
. std::declval
is a helper (added in C++11) that allows us to “pretend” that we have an object of some type (even if default constructor s not available).
If Compute()
cannot be called with T
and T&
objects, then the compiler will SFINAE the whole expression inside void_t
.
Wrapper Code
Equipped with the tool we can now create the following wrapper code:
// helper variable template
template< class T> inline constexpr bool is_compute_available_v =
is_compute_available<T>::value;
template <typename T>
void ComputeTest(T val)
{
if constexpr (is_compute_available_v<T>)
{
T out { };
Compute(val, out);
}
else
{
std::cout << "fallback...\n";
}
}
You can play with code @Coliru
Example - std::from_chars
Ok, so we covered a basic scenario with Compute()
function, but let’s check some more practical example.
How about implementing a fallback for std::from_chars
? This is a robust set of functions that allows fast string to number conversions. I wrote about that feature in my separate article: How to Use The Newest C++ String Conversion Routines.
The problem is that on some compilers (GCC and Clang), as of July 2019 not all conversions are possible. For example, in MSVC you can convert into integral types and also into floating point types, but GCC and Clang offer only integer support.
So we want to implement:
template <typename T>
[[nodiscard]] std::optional<T> TryConvert(std::string_view sv);
The function takes a string view and then returns optional<T>
. The value will be there if the conversion is possible.
ifdefs
In the code samples for my book, I had explicit #ifdefs
to check if the code is compiled on MSVC and if not, then I provided some fallback function. But then, after discussion with Jacek Galowicz (Technical Reviewer) we tried to use templated based approach.
For example, the basic approach is to check the compiler:
// for GCC/Clang:
#ifndef _MSC_VER
template<>
[[nodiscard]] std::optional<double> TryConvert(std::string_view sv) {
// implementation...
}
#endif
This works, but when GCC and clang improve the Standard Library implementations, then I have to adjust the code.
Feature test macros
For new C++ features, we can also check their availability by using feature test macros. They are defined for C++20, but most of the compilers support it already.
For from_chars
we have __cpp_lib_to_chars
.
Still, this feature test is too broad as it won’t tell us about the floating point support. It would be nice to have some distinct “sub” features enabled in this case.
See more test macros @cppreference
Templates - the solution
Let’s try with templates.
Here’s the detection code:
template <typename T, typename = void>
struct is_from_chars_convertible : false_type {};
template <typename T>
struct is_from_chars_convertible<T,
void_t<decltype(from_chars(declval<const char*>(), declval<const char*>(), declval<T&>()))>>
: true_type {};
// std:: omited...
And the function:
template <typename T>
[[nodiscard]] std::optional<T> TryConvert(std::string_view sv) noexcept {
T value{ };
if constexpr (is_from_chars_convertible<T>::value) {
const auto last = sv.data() + sv.size();
const auto res = std::from_chars(sv.data(), last, value);
if (res.ec == std::errc{} && res.ptr == last)
return value;
}
else {
try {
std::string str{ sv };
size_t read = 0;
if constexpr (std::is_same_v<T, double>)
value = std::stod(str, &read);
else if constexpr (std::is_same_v<T, float>)
value = std::stof(str, &read);
if (str.size() == read)
return value;
}
catch (...) { }
}
}
As the fallback code, we’re using stod
or stof
depending on the floating point type. The functions require null-terminated strings, so we have to convert from string view into a string before we pass the parameter. This is not the best approach but might work as a fallback solution.
You can play with the code @Coliru
Add code like std::cout << "fallback...";
to check if a fallback was selected or the proper from_chars
overload.
The code is still not perfect, so I’m happy to see suggestions in the comments. Maybe you can came up with something easier?
Summary
Working with real examples is better in most of the cases, so I like that we could show how the detection pattern works on a real function: std::from_chars
. The full check used various of techniques: SFINAE, void_t
, decltype
, std::declval
, std::true_type
, std::false_type
and partial template specialisation. Plus we even used if constexpr
!
I wonder about the compilation time for such templated code. While the preprocessor approach is old-style and not scalable, it’s super simple, and I guess it offers the best compilation time. Having a single SFINAE detector on a function it usually ok, but what if you have tens or hundreds of such checks? I leave that as an open question.
Do you use detector pattern in your projects? Let us know in comments below!
Here are some good references: