Table of Contents

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

Update 2021: We’ll also throw some C++20 concepts :)

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

C++20 Concepts  

If you can use a C++20 compiler, then we can make our code much shorter!

Thanks to C++20 Concepts there’s no need to use complicated SFINAE syntax.

Our previous example can be specified with the following concept and requires expression:

template<typename T>
concept is_compute_available2 = requires(T v, T& out) {
    Compute(v, out);
};

All we do is to write almost “natural” code that is check at compile time if can be valid.

We can also do it in one line:

template <typename T>
void ComputeTest(T val)
{
    if constexpr (requires(T v, T& out) { Compute(v, out);})
    {
        T out { };
        Compute(val, out);
    }
    else
    {
        std:: cout << "fallback...\n";
    }
}

Play with code @Compiler Explorer

See more in my blog post on Concepts: C++20 Concepts - a Quick Introduction - C++ Stories

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 June 2021 not all conversions are possible. For example, since MSVC 2019 16.4 and GCC 11 you can convert into integral types and also into floating point types, but Clang offers only integer support.

our task is to implement the following helper function:

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

C++17 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 (...) {  }
    }
    
    return std::nullopt;
}

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?

C++20 Concepts Solution  

With Concepts it’s much easier!

See the code:

template <typename T>
concept is_from_chars_convertible = 
    requires (const char* first, const char* last, T& out) {
        std::from_chars(first, last, out);
};

As you can see, we have a simple syntax and almost natural code.

Play with the updated example here @Compiler Explorer

Switch between GCC 11 and GCC 10, or into Clang - and see what code path is instantiated.

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!

Additionally, since it’s 2021, we can leverage the power of C++20 Concepts! The code is super simple and very natural to read and write now.

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: