Martin's Tex-Blog

Posts on programming and generally technical topics

Simple introduction of SFINAE use to fix overloading issues when using forwarding references.

leave a comment »


You have probably read on it in stackoverflow answer, or maybe one of your friends have given you an advice to “SFINAE away those overloads” to fix the overload resolution problems. The SFINAE stands for Substitution Failure Is Not An Error, and in that form is present in the C++ standard. Without this concept it would not be possible to choose correct overloads, consider this example:

http://coliru.stacked-crooked.com/a/79ec2049db08566a

//#1
template<typename T> typename T::value_type foo(T t) {
    // accepts types containing type member of name value_type
    std::cout << "typename T::value_type foo(T t)\n";
    return typename T::value_type{};
}
//#2
int foo(int t) {
    std::cout << "int foo(int t)\n";
    return 0;
}

int main()
{
    foo(1);
    foo(std::vector<int>{});
}

using overload #1 with int type is obviously an error (there is not such thing as int::value_type), but when compiler considers this overload it does not issue an error but only removes this candidate from the list of valid overloads. This is substitution failure is not an error rule in action.

Now lets go back to the topic, with C++11 we have whats called forwarding references, which allow function templates to bind to nearly all types and pass them further. The problem is that they are quite greedy monsters and when you want to overload them with some other types, there is a great chance the overloads will not get called. One of the solutions to this problem is to use tag dispatch. Example on how to use it is presented in below code.

First I present the problem when, greedy eat_all_monster template function with forwarding reference, takes it all. Even thought you would like eat_all_monster(1) to call float overload, it will not happen.

Then there is a code which assigns a type tag to each group of types assigned to specified function overloads. This way you have full control over which overload should be called for which type.

http://coliru.stacked-crooked.com/a/e13dee36eb479174

#include <iostream>
#include <string>
#include <type_traits>

// Version without tag dispatch, shows that for certain types universal 
// reference function overload will steal all calls.
template<typename T>
void eat_all_monster(T&& t) {
    std::cout << t << ", void eat_all_monster(T&& t)\n";
}

void eat_all_monster(float f) {
    std::cout << f << ", void eat_all_monster(float f)\n";
}

void eat_all_monster(const std::string& s) {
    std::cout << s << ", void eat_all_monster(std::string t)\n";
}

void test1() {
   int n = 101;
    std::string s = "123";
    eat_all_monster(n);
    eat_all_monster(s);
    eat_all_monster("12345");
    
    // Above will all call universal reference version of eat_all_monster. 
    // No special overloads will get called.
    //
    // 01, void eat_all_monster(T&& t)
    // 123, void eat_all_monster(T&& t)
    // 12345, void eat_all_monster(T&& t)    
}

// Now, to fix it we will use tag dispatch.

// This is a general tag.
using tag_for_all = std::integral_constant<int, 0>;

// Now, a tag for values, integers, shorts, floats etc.
using tag_for_values = std::integral_constant<int, 1>;

// Tag for texts, const char*, char arrays, std::string
using tag_for_texts = std::integral_constant<int, 2>;

// General trait, will catch all non values, or non texts
template<typename T> struct eat_trait { using tag = tag_for_all; };

// Below two are for specialization for which we have special overloads
template<> struct eat_trait<float>       {    using tag = tag_for_values; };
template<> struct eat_trait<int>         {    using tag = tag_for_values; };
template<> struct eat_trait<std::string> { using tag = tag_for_texts; };
template<> struct eat_trait<const char*> { using tag = tag_for_texts; };

template<typename T>
void eat_some_monster_impl(T&& t, tag_for_all) {
    std::cout << t << ", void eat_some_monster_impl(T&& t)\n";
}

void eat_some_monster_impl(float f, tag_for_values) {
    std::cout << f << ", void eat_some_monster_impl(float f)\n";
}

void eat_some_monster_impl(const std::string& s, tag_for_texts) {
    std::cout << s << ", void eat_some_monster_impl(std::string t)\n";
}

template<typename T>
void eat_some_monster(T&& t) {
    // decay_t is needed because for lvalue-s, T will be a reference (T&), 
    //         and float& and float are two different types
    //         Actually it strips references, cv-qualifications 
    //         (const volatile), and also turns char array to 
    //         pointer to char.
    eat_some_monster_impl(std::forward<T>(t), 
        typename eat_trait<std::decay_t<T>>::tag{});
}

void test2() {
    int n = 101;
    std::string s = "123";
    
    // Now version with tag dispatch will choose the appropriate 
    // overloads based on tags from type traits.
    std::cout << "\n";
    eat_some_monster(n);        // calls value overload (tag_for_values)
                                //  which accepts all values as float
    eat_some_monster(1.0f);     // -- // --
    eat_some_monster(s);        // calls text overload (tag_for_texts) 
                                //  which accepts all texts as std::string 
                                //  (they will convert to it)
    eat_some_monster("12345");  // -- // --
    eat_some_monster((void*)0x1234); // No special overload for void*, so 
                                     //  general version will be called with 
                                     //  tag: tag_for_all    
    
    /*
    // Output is exactly as expected:
    101, void eat_some_monster_impl(float f)
    1, void eat_some_monster_impl(float f)
    123, void eat_some_monster_impl(std::string t)
    12345, void eat_some_monster_impl(std::string t)
    0x1234, void eat_some_monster_impl(T&& t)
    */
}

int main()
{
    test1();
    test2();
}

Advertisements

Written by Marcin

March 14, 2017 at 1:04 am

Posted in C++

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: