2132. std::function ambiguity

Section: 23.14.13.2.1 [func.wrap.func.con] Status: C++14 Submitter: Ville Voutilainen Opened: 2012-02-28 Last modified: 2016-02-10

Priority: 2

View other active issues in [func.wrap.func.con].

View all other issues in [func.wrap.func.con].

View all issues with C++14 status.

Discussion:

Consider the following:

#include <functional>

void f(std::function<void()>) {}
void f(std::function<void(int)>) {}

int main() {
  f([]{});
  f([](int){});
}

The calls to f in main are ambiguous. Apparently because the conversion sequences to std::function from the lambdas are identical. The standard specifies that the function object given to std::function "shall be Callable (20.8.11.2) for argument types ArgTypes and return type R." It doesn't say that if this is not the case, the constructor isn't part of the overload set.

Daniel: During the preparation of N3123 it turned out that there are no longer reasons to refer to INVOKE as a conceptually entity alone, its real implementation as a function template invoke is possible but was deferred for a later point in time. Defining a type trait for the Callable requirement would also be possible, so there seem to be no technical reasons why the template constructor of std::function should not be constrained. The below suggested wording does this without introducing a special trait for this. This corresponds to the way that has been used to specify the result_of trait. Note that the definition of the Callable requirement is perfectly suitable for this, because it is a pure syntactically based requirement and can be directly transformed into a constrained template.

The suggested resolution also applies such wording to the "perfectly forwarding" assignment operator

template<class F> function& operator=(F&&);

The positive side-effect of this is that it automatically implements a solution to a problem similar to that mentioned in issue 1234.

It would be possible to apply similar constraints to the member signatures

template<class F> function& operator=(reference_wrapper<F>);

template<class F, class A> void assign(F&&, const A&);

as well. At this point there does not seem to be a pestering reason to do so.

[2012-10 Portland: Move to Review]

STL: This is a real issue, but does not like a resolution relying on a SFINAEable metafunction that is not specified and available to the users.

packaged_task has the same issue.

STL strongly wants to see an is_callable type trait to clarify the proposed wording.

Jeremiah concerned about holding up what appears to be a correct resolution for a hypothetical better one later - the issue is real.

Why must f by CopyConstructible? Surely MoveConstructible would be sufficient?

Answer: because function is CopyConstructible, and the bound functor is type-erased so must support all the properties of function itself.

Replace various applications of declval in the proposed resolution with simply using the passed functor object, f.

Alisdair to apply similar changes to packaged_task.

[2012-11-09, Vicente J. Botet Escriba provides another example]

Consider the following:

class AThreadWrapper {
public:
  explicit operator std::thread();
  ...
};
std::thread th = std::thread(AThreadWrapper); // call to conversion operator intended

The call to the conversion operator is overloaded with the thread constructor. But thread constructor requirement makes it fail as AThreadWrapper is not a Callable and the compiler tries to instantiate the thread constructor and fails.

[2014-02-14 Issaquah meeting: Move to Immediate]

Proposed resolution:

This wording is relative to N3376.

  1. Change the following paragraphs in 23.14.13.2.1 [func.wrap.func.con]: [Editorial comment: The removal of the seemingly additional no-throw requirements of copy constructor and destructor of A is recommended, because they are already part of the Allocator requirements. Similar clean-up has been suggested by 2070end comment]

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

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

    -?- Remarks: These constructors shall not participate in overload resolution unless f is Callable (23.14.13.2 [func.wrap.func]) for argument types ArgTypes... and return type R.

    […]

    template<class F> function& operator=(F&& f);
    

    -18- Effects: function(std::forward<F>(f)).swap(*this);

    -19- Returns: *this

    -?- Remarks: This assignment operator shall not participate in overload resolution unless declval<typename decay<F>::type&>() is Callable (23.14.13.2 [func.wrap.func]) for argument types ArgTypes... and return type R.