0

According to http://en.cppreference.com/w/cpp/utility/functional/bind, for std::bind

Member function operator()

...

If some of the arguments that are supplied in the call to g() are not matched by any placeholders stored in g, the unused arguments are evaluated and discarded.

Quoting the examples, one can do:

void f(int n1, int n2, int n3, const int& n4, int n5) {
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}

int main() {
    auto f1 = std::bind(f, _2, _1, 42, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
}

If you treat f1 as a function with 5 parameters where 3 are fixed, you shouldn't be able to call f1 with 3 parameters by common sense. However, as shown in above code, you are allowed to do so because the extra parameter is silently ignored.

According to Why do objects returned from bind ignore extra arguments?, this behavior appears to exist because it is convenient to implement??.

To me, this is a rather confusing library feature as it messes up the arity of functions (e.g. 5 - 3 = 3 in above example) and defeats reasoning about function calls. I was wondering if there are any practical use cases in which this kind of behavior is actually beneficial?

More importantly, is it possible to implement a variant of std::bind to forbid this behavior? What are the possibilities and difficulties here.

Thanks

Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • Why not just use std::function directly? – paulm Feb 13 '14 at 18:49
  • `std::bind` operates on `std::function` but is not a `std::function`. IMO, `bind` is like a lazy partial application of functions, with possible parameter switching and this additional (error-prone) behavior. – thor Feb 13 '14 at 19:01
  • "If you treat the bind result f1 above as a function or converted to a std::function, you wouldn't be able to call f1 with the wrong number of parameters." False. If you bind or convert it to `function`, you would be able to call it with three parameters, just as your example does. – Igor Tandetnik Feb 13 '14 at 19:58
  • Refined/corrected the statement to state my point in terms of common sense. – thor Feb 13 '14 at 22:21
  • or in functional terms. – thor Feb 13 '14 at 22:29
  • Is lambda enough as a solution? – milleniumbug Feb 13 '14 at 22:32
  • Isn't lambda like `std::function`, something manipulated by `bind` but itself not a `bind` like operation? – thor Feb 13 '14 at 22:34
  • Something like this? http://coliru.stacked-crooked.com/a/14f42290ddb53cb8 – dyp Feb 13 '14 at 23:02
  • Btw, cppreference's page for [`is_bind_expression`](http://en.cppreference.com/w/cpp/utility/functional/is_bind_expression) shows another reason why accepting additional arguments may be intended. – dyp Feb 13 '14 at 23:03
  • @dyp, Thanks for your code. I looks indeed like what I am looking for. Is it true your code has only one call `operator()`? If so, that would be a great feature. – thor Feb 13 '14 at 23:07
  • Also, I was wondering if it is possible/not too cumbersome to revise your code such that it applies the function when all parameters of the bind target is bound. If so, that will make strict_bind also strict in the sense of function application. http://stackoverflow.com/questions/21247882/c11-bind-and-apply?rq=1 – thor Feb 13 '14 at 23:10
  • @dyp In any case, if you make an answer, it will what my question is looking for. – thor Feb 13 '14 at 23:11
  • 1
    My `strict_binder` has a function template `operator()`, i.e. it has a (potential) overload set of `operator()`s. -- It is possible and quite simple (via tag dispatch) to rewrite the `strict_bind` to call the function instead of returning a binder *if there are no place-holder arguments*: http://coliru.stacked-crooked.com/a/35b66c5b59050f1c – dyp Feb 13 '14 at 23:18
  • This is perfect. Thanks. I wish I could accept your comments as an answer. – thor Feb 13 '14 at 23:22
  • I'll post an answer tomorrow ;) – dyp Feb 13 '14 at 23:29
  • @dyp When you do, do you mind including both versions of `strict_bind`?, say `strict_bind` and `apply`. Thanks a lot. – thor Feb 13 '14 at 23:48

1 Answers1

1

Note: Restricting the number of arguments breaks a feature of binders that is illustrated here.

My solution is based on counting the passed placeholders determining the greatest placeholder used. Thanks to Xeo for pointing out this error.

#include <functional>
#include <type_traits>
#include <utility>

template<class T, class U>
constexpr auto c_max(T&& t, U&& u)
-> typename std::remove_reference<decltype( t > u ? t : u )>::type
{  return t > u ? t : u;  }

template<class...>
struct max_placeholder : std::integral_constant<int, 0> {};

template<class T, class... Rest>
struct max_placeholder<T, Rest...>
: std::integral_constant<int, c_max(std::is_placeholder<T>::value,
                                    max_placeholder<Rest...>::value)>
{};

This lays the burden to correctly count the number on the user of the binder. For some bound Callables such as function pointers, it is possible to deduce the number of arguments (this also allows automatically supplying the required amount of placeholders). Once you have fixed the number of arguments either way, it's easy to write a wrapper that stores a binder and provides an operator() template that checks the number of arguments:

template<class T, int N>
struct strict_binder
{
    T binder;

    template<class... Args>
    auto operator()(Args&&... args)
    -> decltype( binder(std::forward<Args>(args)...) )
    {
        static_assert(sizeof...(args) == N, "wrong number of arguments");
        return binder(std::forward<Args>(args)...);
    }
};

It is also possible to produce a substitution failure instead of an error.

As the strict_binder is a binder, you can express this concept via a partial specialization:

namespace std
{
    template<class T, int N>
    struct is_bind_expression< strict_binder<T, N> >
        : public true_type
    {};
}

All that remains is to write a function template that produces strict_binders. Here's a version that's similar to std::bind:

template<class F, class... Args>
auto strict_bind(F&& f, Args&&... args)
-> strict_binder<
       typename std::decay<
            decltype( std::bind(std::forward<F>(f), std::forward<Args>(args)...) )
       >::type,
       max_placeholder<typename std::remove_reference<Args>::type...>::value
   >
{
    return { std::bind(std::forward<F>(f), std::forward<Args>(args)...) };
}

Essentially, the return type is a

strict_binder<decltype(std::bind(f, args...)), count_placeholders<Args...>::value>

That is, the strict_binder stores the resulting type of std::bind.


You can also write an apply-like function that calls the bound function when no placeholders have been passed:

template<int N, class F, class... Args>
auto strict_bind_or_call(std::integral_constant<int, N>, F&& f, Args&&... args)
-> strict_binder<
       typename std::decay<
            decltype( std::bind(std::forward<F>(f), std::forward<Args>(args)...) )
       >::type,
       N
   >
{
    return { std::bind( std::forward<F>(f), std::forward<Args>(args)... ) };
}

template<class F, class... Args>
auto strict_bind_or_call(std::integral_constant<int, 0>, F&& f, Args&&... args)
-> decltype( std::bind( std::forward<F>(f), std::forward<Args>(args)... ) () )
{
    return std::bind( std::forward<F>(f), std::forward<Args>(args)... ) ();
}

template<class F, class... Args>
auto strict_bind(F&& f, Args&&... args)
-> decltype( strict_bind_or_call( std::integral_constant<int, max_placeholder<typename std::remove_reference<Args>::type...>::value>{},
                                  std::forward<F>(f), std::forward<Args>(args)... ) )
{
    using max_placeholder_here =
        max_placeholder<typename std::remove_reference<Args>::type...>;

    return strict_bind_or_call( max_placeholder_here{},
                                std::forward<F>(f), std::forward<Args>(args)... );
}

This uses tag dispatch to either return a binder or the result of calling the function. I gave up formatting this properly, you might want to introduce alias templates in a detail namespace.

Note the decltype( std::bind(..) () ) in the second overload of strict_bind_or_call is a simple way to reproduce the semantics of INVOKE / bind; I can't just write f(args...) because f might be a member function.


Usage example:

#include <iostream>

void foo(int p0, int p1)
{ std::cout << "[" << p0 << ", " << p1 << "]\n"; }

int main()
{
    auto f0 = strict_bind(foo, std::placeholders::_1, 42);
    f0(1);

    strict_bind(foo, 1, 2);
}
Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Note that `is_placeholder` does not just return 0 or 1, so for `count_placeholders<_1_t, _2_t, _3_t>`, you would get 6. You also need not the number of placeholders, but the value of the highest placeholder, i.e. a `max` meta-function. If the highest placeholder is `_3`, well, then you need 3 arguments, even if there is no `_1` or `_2`. – Xeo Feb 14 '14 at 22:57