Why doesn't std::bind account for function arity?

前端 未结 1 1462
北荒
北荒 2021-01-12 01:17

If I have this simple case:

struct Foo 
{
    void bar();
    void baz(int );
};

It makes sense that this would compile:

Fo         


        
相关标签:
1条回答
  • 2021-01-12 01:18

    But why would bind be designed in such a way that this compiles:
    auto g = std::bind(&Foo::baz, &foo);
    I can call f, but I cannot ever call g. Why even make that compile?

    The Boost.Bind FAQ says that Boost.Bind will usually diagnose such errors at "bind time" (i.e. on the line where you call bind). However the standard doesn't require that for std::bind, instead it has the following in the Requires element for std::bind:

    INVOKE (fd, w1, w2, ..., wN) (20.9.2) shall be a valid expression for some values w1, w2, ..., wN, where N == sizeof...(bound_args).

    This means your code violates the function's precondition, which results in undefined behaviour. The standard library implementation is not obliged to check for precondition violations, that's your job. The library isn't forbidden to check them either, so it would be conforming for an implementation to reject it, as Boost.Bind does. I would make a request to your library vendor asking them to diagnose invalid bind expressions where it is possible to do so, as a "Quality of Implementation" enhancement.

    why not just have the default pass all the arguments in the right order without having to specify it?

    I can think of two reasons.

    Firstly, the behaviour of the call wrapper created by bind is to drop arguments that do not correspond to a placeholder, so you can call x(1, 2, 3) and have it ignore all the arguments and call foo.bar(). This is part of a general pattern where you can wrap an N-arity function using bind to create a call wrapper with a completely different arity that might add arguments, remove them, fix some to specific bound values etc. It would be impossible to have x(1, 2, 3) drop all arguments if the default behaviour when no placeholders are used in the bind expression was to forward all the arguments.

    Secondly, it's more consistent to always require you to be explicit about which arguments you want passed in which order. In general it would only make sense to pass all the invocation arguments when there are no bound arguments, otherwise how should bind know whether to pass the invocation arguments before or after the bound arguments?

    e.g. given

    struct X {
      void f(int, int) { }
    } x;
    auto h = bind(&X::f, &x, 1);
    h(2);
    

    Should the call to h(2) result in x.f(1, 2) or x.f(2, 1)? Since the correct behaviour when there are bound arguments is not obvious, automatically forwarding all arguments when there were no placeholders used only really makes sense when there are no bound arguments (because then there's no question of whether the bound arguments should come first or last), which is a fairly special case. Changing a significant feature of the API to work with that special case would be of questionable value, especially when it makes the x(1, 2, 3) -> foo.bar() case impossible to achieve.

    An alternative solution is to continue requiring users to be explicit about what they want, but provide an explicit way to say "just forward everything", as proposed by Tomasz Kamiński in N4171 which will be discussed at the C++ committee meeting next week. The _all placeholder solves the problem of deciding whether the invocation arguments should come before or after the bound arguments, because you can explicitly say whether you want bind(f, arg1, arg2, std::placeholders::_all) or bind(f, std::placeholders::_all, arg1, arg2) or even bind(f, arg1, std::placeholders::_all, arg2)

    0 讨论(0)
提交回复
热议问题