Overload resolution with std::function

后端 未结 2 571
你的背包
你的背包 2020-12-01 14:57

Consider this example of code:

#include 
#include 

typedef std::function func1_t;
typedef std::function

        
2条回答
  •  一向
    一向 (楼主)
    2020-12-01 15:08

    In C++11...

    Let's take a look at the specification of the constructor template of std::function (which takes any Callable): [func.wrap.func.con]/7-10

    template function(F f);
    template  function(allocator_arg_t, const A& a, F f);
    

    7 Requires: F shall be CopyConstructible. f shall be Callable (20.10.11.2) for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

    8 Postconditions: !*this if any of the following hold:

    • f is a NULL function pointer.
    • f is a NULL pointer to member.
    • F is an instance of the function class template, and !f

    9 Otherwise, *this targets a copy of f initialized with std::move(f). [left out a note here]

    10 Throws: shall not throw exceptions when f is a function pointer or a reference_wrapper for some T. Otherwise, may throw bad_alloc or any exception thrown by F’s copy or move constructor.

    Now, constructing, or attempting to construct (for overload resolution) a std::function from a [](){} (i.e. with signature void(void)) violates the requirements of std::function's constructor.

    [res.on.required]/1

    Violation of the preconditions specified in a function’s Requires: paragraph results in undefined behavior unless the function’s Throws: paragraph specifies throwing an exception when the precondition is violated.

    So, AFAIK, even the result of the overload resolution is undefined. Therefore, both versions of g++/libstdc++ are complying in this aspect.


    In C++14, this has been changed, see LWG 2132. Now, the converting constructor template of std::function is required to SFINAE-reject incompatible Callables (more about SFINAE in the next chapter):

    template function(F f);
    template  function(allocator_arg_t, const A& a, F f);
    

    7 Requires: F shall be CopyConstructible.

    8 Remarks: These constructors shall not participate in overload resolution unless f is Callable (20.9.11.2) for argument types ArgTypes... and return type R.

    [...]

    The "shall not participate in overload resolution" corresponds to rejection via SFINAE. The net effect is that if you have an overload set of functions foo,

    void foo(std::function);
    void foo(std::function);
    

    and a call-expression such as

    foo([](std::string){}) // (C)
    

    then the second overload of foo is chosen unambiguously: Since std::function defines F as its interface to the outside, the F defines which argument types are passed into std::function. Then, the wrapped function object has to be called with those arguments (argument types). If a double is passed into std::function, it cannot be passed on to a function taking a std::string, because there's no conversion double -> std::string. For the first overload of foo, the argument [](std::string){} is therefore not considered Callable for std::function. The constructor template is deactivated, hence there's no viable conversion from [](std::string){} to std::function. This first overload is removed from the overload set for resolving the call (C), leaving only the second overload.

    Note that there's been a slight change to the wording above, due to LWG 2420: There's an exception that if the return type R of a std::function is void, then any return type is accepted (and discarded) for the Callable in the constructor template mentioned above. For example, both []() -> void {} and []() -> bool {} are Callable for std::function. The following situation therefore produces an ambiguity:

    void foo(std::function);
    void foo(std::function);
    
    foo([]() -> bool {}); // ambiguous
    

    The overload resolution rules don't try to rank among different user-defined conversions, and hence both overloads of foo are viable (first of all) and neither is better.


    How can SFINAE help here?

    Note when a SFINAE-check fails, the program isn't ill-formed, but the function isn't viable for overload resolution. For example:

    #include 
    #include 
    
    template
    auto foo(T) -> typename std::enable_if< std::is_integral::value >::type
    {  std::cout << "foo 1\n";  }
    
    template
    auto foo(T) -> typename std::enable_if< not std::is_integral::value >::type
    {  std::cout << "foo 2\n";  }
    
    int main()
    {
        foo(42);
        foo(42.);
    }
    

    Similarly, a conversion can be made non-viable by using SFINAE on the converting constructor:

    #include 
    #include 
    
    struct foo
    {
        template::value >::type >
        foo(T)
        {  std::cout << "foo(T)\n";  }
    };
    
    struct bar
    {
        template::value >::type >
        bar(T)
        {  std::cout << "bar(T)\n";  }
    };
    
    struct kitty
    {
        kitty(foo) {}
        kitty(bar) {}
    };
    
    int main()
    {
        kitty cat(42);
        kitty tac(42.);
    }
    

提交回复
热议问题