Question

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

Was it helpful?

Solution

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);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top