C++17 In Detail

08 July 2019

How To Detect Function Overloads in C++17, std::from_chars Example

Detect Function Overload, C++17

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:

Get my free ebook about C++17!

More than 50 pages about the new Language Standard.

C++17 in detail, by Bartlomiej Filipek

C++17 In Detail
© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.