Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
325 views
in Technique[技术] by (71.8m points)

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

If I have this simple case:

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

It makes sense that this would compile:

Foo foo;
auto f = std::bind(&Foo::bar, &foo);

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? What is the rationale behind requiring me to have to do:

auto g2 = std::bind(&Foo::baz, &foo, std::placeholders::_1);

I can understand using the placeholders if you want to mess with which arguments get passed and in what order, but why not just have the default pass all the arguments in the right order without having to specify it?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...