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

前端 未结 3 965
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-02 22:16

Given:

#include 
class world_building_gun;
class tile_bounding_box;
typedef std::function

        
3条回答
  •  佛祖请我去吃肉
    2021-01-02 22:45

    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()..., 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()...)

    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 where some incomplete types are involved in Sig then we could define this operation as being 'first complete the types, then treat std::function 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;
    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.

提交回复
热议问题