Does std::function's copy-constructor require the template type's argument types to be complete types?

与世无争的帅哥 提交于 2019-11-30 14:51:16
Xeo

Edit: Apperently, this issue is now fixed, so the below text can be seen as history. :)


The issue is indeed (as I predicted) with libc++'s SFINAE checks in the templated ctor (for a reasoning, check this question). It checks if the following (for example) is valid and gives a nice and clean error at the construction site rather than deep inside the guts of std::function (try the following example with libstd++ or MSVC... shudder):

#include <functional>

void f(int* p){}

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

libc++ will cause the compiler to spit out something along the lines of "no constructor found that matches the argument list void (*)(int*)", since the only applicable one (the templated ctor) gets SFINAE'd out.

However, so that the __callable and __invoke_imp checks work, the argument and return types need to be complete, since otherwise implicit conversions wouldn't be taken into account here.

The reason that the templated ctor is even looked at is that all ctors are enumerated before considering a best match (in this case the copy ctor).


Now, the standard is very clear that the argument and return types need to be complete when constructing a std::function object from a callable object (aka calling the templated ctor):

§20.8.11.2.1 [func.wrap.func.con] p7

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

Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. [...]

(Note: "Requires" addresses the user of the functionality, not the implementer.)

§20.8.11.2 [func.wrap.func] p2

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.8.2).

§20.8.2 [func.req]

p1 Define INVOKE(f, t1, t2, ..., tN) as follows:

  • (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;
  • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;
  • [...]
  • f(t1, t2, ..., tN) in all other cases.

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

So, libc++ is certainly within its rights to do the SFINAE check in the templated ctor, since the types need to be complete, since otherwise you'd get undefined behaviour. However, it may be a bit unfortunate and be considered a defect that the safety check for a complete type triggers even if the actual SFINAE check is never needed (because the copy ctor will always be invoked). This may be alleviated by making the callable check a lazy one, like

template<bool Copy, class F>
struct lazy_callable{
  static bool const value = callable<F>::value;
};

template<class F>
struct lazy_callable<true, F>{
  static bool const value = false;
};

template<class F>
function(F f, typename enable_if<lazy_callable<!std::is_same<F,function>::value>::type* = 0);

This should only trigger the callable SFINAE check if F is not actually std::function<...>.

Man, I may have digressed a bit here at the end...

I've committed a fix to libc++ such that this example now compiles, committed revision 160285.

I believe libc++ was being overly aggressive in checking the argument list for complete types.

I'd say no. From 20.8.11.2 Class template function [func.wrap.func], we have:

3 The function class template is a call wrapper (20.8.1) whose call signature (20.8.1) is R(ArgTypes...).

In 20.8.1 Definitions [func.def ], we get the following definitions as to what constitutes a call wrapper type, a call wrapper and a call signature:

2 A call signature is the name of a return type followed by a parenthesized comma-separated list of zero or more argument types.

5 A call wrapper type is a type that holds a callable object and supports a call operation that forwards to that object.

6 A call wrapper is an object of a call wrapper type.

Notice how paragraph 2 doesn't mention completeness of types.

To cut a story short (a lot of definitions are involved), the meaning of 'callable object' here means either a functor (the familiar notion, i.e. something that can be used like a function) or a pointer-to-member. Furthermore the Standard also describe the Callable concept in 20.8.11.2 paragraph 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.8.2).

(The INVOKE bit is an imaginary function that the Standard uses to define how functors and pointers to members are, well, invoked.)

What I think is the most important conclusion from that is the following requirement:

  • given a callable object that is Callable with signature R(A...), then R and A... are complete (or possibly R is void) by virtue of the INVOKE expression (i.e. otherwise it would not be well-formed, notice the use of declval<ArgTypes>()...)

My argument now rests on '[the] call operation that forwards to that object' in the definition of call wrappers, which I think is intentionally left vague so as to not be overly restrictive. In the case of std::function<Sig> where some incomplete types are involved in Sig then we could define this operation as being 'first complete the types, then treat std::function<Sig> as a callable object type of call signature Sig'.

Given that, here are the key points of my argument:

  • std::function is not described as being a callable object or as being Callable for any signature
  • invoking an std::function is done in terms of INVOKE (20.8.11.2.4 function invocation [func.wrap.func.inv])
  • constructing an std::function from a callable object is in terms of Callable with the call signature of std::function (20.8.11.2.1 function construct/copy/destroy [func.wrap.func.con] paragraph 7)
  • calling the target member of std::function is in terms of Callable with the call signature of std::function (20.8.11.2.5 function target access [func.wrap.func.targ])
  • all other operations of std::function are not described in terms of callable object(*) , Callable, INVOKE or otherwise require that the call signature of std::function involve complete types

(*) except in the case of one constructor where the description contains "shall not throw exceptions if f’s target is a callable object passed via reference_wrapper or a function pointer". I think in the context it's clear that this doesn't affect the argument. For what it's worth this constructor is not involved in the snippet of the OP.

So I'd say unless you do use one of those operations that indirectly require the signature to involve complete types, you're good to go.


It's all well and good to analyze what the Standard prescribes but it's also important to consider what is the intent of the Standard. In this case I think it is very much desirable and expected that std::function does not require that the types of the call signature be complete. Consider the following:

// in a_fwd.hpp
struct incomplete;
using callback_type = std::function<void(incomplete)>;
callback_type make_callback();

// in b.hpp; depends on a_fwd.hpp
#include "a_fwd.hpp"
void eat_callback(callback_type);

Then without the requirement an unrelated TU, let's call it C, that is a client of B can do:

// in c.cpp
#include "b.hpp"

// somewhere in e.g. a function body
eat_callback(make_callback());

This is type-safe and minimizes the coupling since only translation unit B need to know about the details of translation unit A.

Furthermore both Boost.Function and libstdc++ have demonstrated that it is possible to implement std::function without such a requirement.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!