c++templatesoverloadingstd-functionoverload-resolution

Function template overload with std::function type parameter, nullptr value and shared_ptr type


In the example below, why does the last one calls the function overload with std::function as parameter?

#include <iostream>
#include <functional>
#include <memory>

template <class Type>
void make_something(Type&& a){
    std::cout<<"Type&& overload"<<std::endl;
}

template <class Type>
void make_something(std::function<Type()>){
    std::cout<<"std::function overload"<<std::endl;
}


int main(){ 
    make_something<int>(1);  // prints "Type&& overload"
    make_something<int>(nullptr);  // prints "std::function overload"
    make_something<int*>(nullptr);  // prints "Type&& overload"

    using ptr_int = std::shared_ptr<int>;
    make_something<ptr_int>(nullptr);  // prints "std::function overload" ... why?
}

Solution

  • There is an implicit conversion from std::nullptr_t to both std::shared_ptr<int> and std::function<std::shared_ptr<int>()>.

    This means that calling make_something<ptr_int>(nullptr) needs to do the same amount of conversions to transform the std::nullptr_t argument into the function argument (a user-defined conversion sequence).

    If these were both non-template functions, this would be ambiguous. Since they are templates, the tiebreakers for templates can be used.

    std::function<Type()> is more specialised than Type (cv- and ref- qualifications are discarded for this check). This means that std::function<Type()> overload is chosen.

    If you were to add a third more specialized overload, that would be chosen:

    template <class Type>
    void make_something(std::function<Type*()>){
        std::cout<<"std::function Type* overload"<<std::endl;
    }
    

    This is generally used when the type is deduced (e.g., if you called make_something(std::function<int()>{}), it would be ambiguous without the template rules), but has this unexpected behaviour when you specify the template argument.