0

http://en.cppreference.com/w/cpp/utility/functional/bindによると、std::bind

メンバー関数 operator()

...

g() の呼び出しで指定された引数の一部が、g に格納されているプレースホルダーと一致しない場合、未使用の引数が評価されて破棄されます。

例を引用すると、次のことができます。

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
}

3つ固定のところを5つのパラメータを持つ関数として扱うと、常識的に3つのパラメータでf1呼び出すことはできないはずです。f1ただし、上記のコードに示されているように、余分なパラメーターは黙って無視されるため、そうすることができます。

なぜバインドから返されたオブジェクトは余分な引数を無視するのですか? 、実装するのが便利なため、この動作が存在するようです??.

私にとって、これは関数のアリティ (上記の例では 5 - 3 = 3) を台無しにし、関数呼び出しについての推論を無効にするため、やや混乱するライブラリ機能です。この種の動作が実際に有益な実用的なユースケースがあるかどうか疑問に思っていましたか?

std::bindさらに重要なことに、この動作を禁止するバリアントを実装することは可能ですか? ここでの可能性と困難は何ですか。

ありがとう

4

1 に答える 1

1

注: 引数の数を制限すると、ここに示されているバインダーの機能が損なわれます。

私の解決策は、渡されたプレースホルダーを数えて、使用される最大のプレースホルダーを決定することに基づいています。このエラーを指摘してくれたXeoに感謝します。

#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)>
{};

これは、バインダーのユーザーに数を正しく数える負担を課します。関数ポインターなどの一部のバインドされた Callable では、引数の数を推測することができます (これにより、必要な量のプレースホルダーを自動的に提供することもできます)。いずれかの方法で引数の数を固定したら、バインダーを格納し、operator()引数の数をチェックするテンプレートを提供するラッパーを簡単に作成できます。

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)...);
    }
};

エラーの代わりに置換失敗を生成することもできます。

strict_binderであるためbinder、部分的な特殊化によってこの概念を表現できます。

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

あとは、s を生成する関数テンプレートを作成するだけstrict_binderです。に似たバージョンを次に示し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)...) };
}

基本的に、戻り値の型は

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

つまり、 はstrict_binderの結果の型を格納しstd::bindます。


applyプレースホルダーが渡されていない場合に、バインドされた関数を呼び出す のような関数を作成することもできます。

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)... );
}

これは、タグ ディスパッチを使用して、バインダーまたは関数呼び出しの結果を返します。これを適切にフォーマットすることをあきらめましたdetail。名前空間にエイリアス テンプレートを導入することをお勧めします。

decltype( std::bind(..) () )2 番目のオーバーロードのは、 /strict_bind_or_callのセマンティクスを再現する簡単な方法であることに注意してください。メンバー関数の可能性があるため、書くことはできません。INVOKEbindf(args...)f


使用例:

#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);
}
于 2014-02-14T20:30:01.433 に答える