Why assignment to std::function doesn't com

2020-05-23 03:02发布

问题:

The following code doesn't compile:

#include <functional>

struct X
{
    std::function<X()> _gen;
};

int main()
{
    X x;
    x._gen = [] { return X(); }; //this line is causing problem!
}

I don't understand why assignment to x._gen is causing problem. Both gcc and clang are giving similar error messages. Could anyone please explain it?


Compiler error messages

GCC's error:

In file included from main.cpp:1:0:
/usr/include/c++/4.8/functional: In instantiation of ‘std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_CheckResult<std::function<_Res(_ArgTypes ...)>::_Invoke<_Functor>, _Res>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = main()::__lambda0; _Res = X; _ArgTypes = {}; std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_CheckResult<std::function<_Res(_ArgTypes ...)>::_Invoke<_Functor>, _Res>, std::function<_Res(_ArgTypes ...)>&> = std::function<X()>&]’:
main.cpp:11:12:   required from here
/usr/include/c++/4.8/functional:2333:4: error: no matching function for call to ‘std::function<X()>::function(main()::__lambda0)’
    function(std::forward<_Functor>(__f)).swap(*this);
    ^
/usr/include/c++/4.8/functional:2333:4: note: candidates are:
/usr/include/c++/4.8/functional:2255:2: note: template<class _Functor, class> std::function<_Res(_ArgTypes ...)>::function(_Functor)
  function(_Functor);
  ^
/usr/include/c++/4.8/functional:2255:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.8/functional:2230:7: note: std::function<_Res(_ArgTypes ...)>::function(std::function<_Res(_ArgTypes ...)>&&) [with _Res = X; _ArgTypes = {}]
       function(function&& __x) : _Function_base()
       ^
/usr/include/c++/4.8/functional:2230:7: note:   no known conversion for argument 1 from ‘main()::__lambda0’ to ‘std::function<X()>&&’
/usr/include/c++/4.8/functional:2433:5: note: std::function<_Res(_ArgTypes ...)>::function(const std::function<_Res(_ArgTypes ...)>&) [with _Res = X; _ArgTypes = {}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/include/c++/4.8/functional:2433:5: note:   no known conversion for argument 1 from ‘main()::__lambda0’ to ‘const std::function<X()>&’
/usr/include/c++/4.8/functional:2210:7: note: std::function<_Res(_ArgTypes ...)>::function(std::nullptr_t) [with _Res = X; _ArgTypes = {}; std::nullptr_t = std::nullptr_t]
       function(nullptr_t) noexcept
       ^
/usr/include/c++/4.8/functional:2210:7: note:   no known conversion for argument 1 from ‘main()::__lambda0’ to ‘std::nullptr_t’
/usr/include/c++/4.8/functional:2203:7: note: std::function<_Res(_ArgTypes ...)>::function() [with _Res = X; _ArgTypes = {}]
       function() noexcept
       ^
/usr/include/c++/4.8/functional:2203:7: note:   candidate expects 0 arguments, 1 provided

Likewise, Clang throws this:

main.cpp:11:12: error: no viable overloaded '='
    x._gen = [] { return X(); };
    ~~~~~~ ^ ~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2270:7: note: candidate function not viable: no known conversion from '<lambda at main.cpp:11:14>' to 'const std::function<X ()>' for 1st argument
      operator=(const function& __x)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2288:7: note: candidate function not viable: no known conversion from '<lambda at main.cpp:11:14>' to 'std::function<X ()>' for 1st argument
      operator=(function&& __x)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2302:7: note: candidate function not viable: no known conversion from '<lambda at main.cpp:11:14>' to 'nullptr_t' for 1st argument
      operator=(nullptr_t)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2192:39: note: candidate template ignored: disabled by 'enable_if' [with _Functor = <lambda at main.cpp:11:14>]
        using _Requires = typename enable_if<_Cond::value, _Tp>::type;
                                             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2340:2: note: candidate template ignored: could not match 'reference_wrapper<type-parameter-0-0>' against '<lambda at main.cpp:11:14>'
        operator=(reference_wrapper<_Functor> __f) noexcept
        ^

回答1:

This was PR60594, which got fixed in GCC 4.8.3. The comments on that bug point out why it is valid: although the standard requires template arguments for standard library templates to be a complete type (with some exceptions), X() is a complete type even if X is not.

There are several members of std::function<X()> that do implicitly require X to be a complete type. The template constructor you're using is one of them: it requires the return type of your lambda to be implicitly convertible to X, but whether X is convertible to itself depends on whether X is a complete type: if it's incomplete, the compiler can't rule out the possibility that it is an uncopyable unmovable type.

This requirement follows from:

20.9.11.2.1 function construct/copy/destroy [func.wrap.func.con]

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.

20.9.11.2 Class template function [func.wrap.func]

2 A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.9.2).

20.9.2 Requirements [func.require]

2 Define INVOKE(f, t1, t2, ..., tN, R) as INVOKE(f, t1, t2, ..., tN) implicitly converted to R.

Several other members of std::function also require X to be a complete type.

You're only using that constructor after type X has already completed, though, so there's no problem: at that point, X certainly can be implicitly converted to X.

The problem was that std::function was performing checks that depend on X being a complete type, in a context where the standard doesn't support performing such checks, and this did not account for the possibility that X would become a complete type after the instantiation of std::function<X()> had already completed.



回答2:

This may be a gcc bug, but maybe not. It isn't directly in = but rather in the conversion constructor for std::function (which the operator= invokes).

Here is a pathological example of it happening:

#include <iostream>
#include <functional>

struct X
{
  std::function<X()> _gen;
};

X func() {return {};};

int main()
{
  std::function<X()> foo1( &func ); // compiles
  X unused = X{}; // copy ctor invoked
  std::function<X()> foo2( &func ); // does not compile!
}

note that the first foo1 works fine, it is not until I cause some code somewhere to invoke the copy ctor that the second one generates errors. Even auto unused =[]{ return X{}; }; is enough. (func direct constructs and never copies).

It is the use/"creation" of the copy ctor that seems to cause the problem.

#include <iostream>
#include <functional>

struct X
{
  std::function<X()> _gen;
  X( X const& ) = default;
  X() = default;
};
X func() {return {};};

int main()
{
  std::function<X()> foo1( &func ); // does not compile
}

that copy constructor ends up calling the copy ctor of _gen, possibly before X is a complete type.

If we explicitly delay instantiation of X::X(X const&) until X is a complete type:

#include <functional>

struct X
{
  std::function<X()> _gen;
  X( X const& );
  X() {}
};
X::X( X const& o ):_gen(o._gen){} // or =default *here*

X func() {return {};};

int main()
{
  std::function<X()> foo1( &func ); // compiles!
  []{ return X{}; }; // or X unused = X{};
  std::function<X()> foo2( &func ); // compiles!
}

the problem goes away.

I suspect that the implicit copy constructor of X created in the body of X when X is an incomplete type implicitly invokes std::function<X()>'s copy constructor, which is in a context where X is incomplete, which breaks the preconditions of its copy constructor being invoked (at least in practice in how it was implemented in gcc -- by the standard? I am unsure.)

By explicitly making a copy ctor outside of X I avoid this, and everything works.

So as a work around to your problem, declare and implement X::X(X const&) outside of X, and the magic error goes away.