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

StackOverflow https://stackoverflow.com/questions/11480642

  •  20-06-2021
  •  | 
  •  

Question

Given:

#include <functional>
class world_building_gun;
class tile_bounding_box;
typedef std::function<void (world_building_gun, tile_bounding_box)> worldgen_function_t;
void foo() {
    worldgen_function_t v;
    worldgen_function_t w(v);
}

Should this compile? My compilers say:

Yes: GCC/stdlibc++ (also boost::function is yes in both GCC and Clang)

No: Clang/libc++ ( http://libcxx.llvm.org/ , Clang 3.0, libc++ SVN as of today)

(If "no" is the correct answer, I will fix my real code to put complete types in more headers or use boost::function.)

EDIT: Here is the Clang error message:

In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid appli
    static_assert(sizeof(_Tp) > 0, "Type must be complete.");
                  ^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2752:15: note: in instantiation of template class 'std::__1::__check_complete<world_buildin
    : private __check_complete<_Hp>,
              ^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<world_buildin
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class 'std::__1::__check_complete<std::__1::fun
      world_building_gun, tile_bounding_box>' requested here
    : private __check_complete<_Fp, _Args...>
              ^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class 'std::__1::__invokable_imp<std::__1::func
      world_building_gun, tile_bounding_box>' requested here
          __invokable_imp<_Fp, _Args...>::value>
          ^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class 'std::__1::__invokable<std::__1::function<
      world_building_gun, tile_bounding_box>' requested here
    template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
                                ^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for '__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >' required here
              typename enable_if<__callable<_Fp>::value>::type* = 0);
                                  ^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template 'function' [with _Fp = std::__1::function<void
      (world_building_gun, tile_bounding_box)>]
      function(_Fp,
      ^
foo.cpp:4:7: note: forward declaration of 'world_building_gun'
class world_building_gun;
      ^
In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid application of 'sizeof' to an incomplete type 'tile_bounding_box'
    static_assert(sizeof(_Tp) > 0, "Type must be complete.");
                  ^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<tile_bounding_box>' requested here
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<world_building_gun, tile_bounding_box>' requested here
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class 'std::__1::__check_complete<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
    : private __check_complete<_Fp, _Args...>
              ^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class 'std::__1::__invokable_imp<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
          __invokable_imp<_Fp, _Args...>::value>
          ^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class 'std::__1::__invokable<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
    template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
                                ^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for '__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >' required here
              typename enable_if<__callable<_Fp>::value>::type* = 0);
                                  ^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template 'function' [with _Fp = std::__1::function<void
      (world_building_gun, tile_bounding_box)>]
      function(_Fp,
      ^
foo.cpp:5:7: note: forward declaration of 'tile_bounding_box'
class tile_bounding_box;
      ^
2 errors generated.

Clang+libc++ compiles successfully if I delete the line "worldgen_function_t w(v);" or if I make the classes complete types.

Was it helpful?

Solution

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

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top