4

私はこのプログラムを正しくコンパイルしようとしています:

#include <vector>
#include <iostream>

int f(int a, int b)
{
   ::std::cout << "f(" << a << ", " << b << ") == " << (a + b) << '\n';
   return a + b;
}

template <typename R, typename V>
R bind_vec(R (*f)(), const V &vec, int idx=0)
{
   return f();
}

template <typename R, typename V, typename Arg1, typename... ArgT>
R bind_vec(R (*f)(Arg1, ArgT...), const V &vec, int idx=0)
{
   const Arg1 &arg = vec[idx];
   auto call = [arg, f](ArgT... args) -> R {
      return (*f)(arg, args...);
   };
   return bind_vec(call, vec, idx+1);
}

int foo()
{
   ::std::vector<int> x = {1, 2};
   return bind_vec(f, x);
}

bind_vec理想的には、関数ポインタだけでなく、任意のファンクタを引数として取りたいと思います。::std::vectorアイデアは、コンパイル時にから関数の引数をプルすることです。

これはこれの最終的な使用法ではありませんが、私が行きたいところへの足がかりです。私が実際に行っているのは、コンパイル時にfuture/promise型システムでpromiseから引数をアンラップするラッパー関数を生成することです。これらのラッパー関数自体がpromiseになります。

私の究極のユースケースでは、ファンクターが::std::functionsであると期待できます。しかし、これは広く興味深い問題だと思うので、より一般的なファンクターに対してもどのように機能するかを考えておくとよいでしょう。

4

3 に答える 3

5

まず、ファンクターのアリティを検出することはできますが、少し複雑なので、別の質問に任せるのが最善です。呼び出しでファンクタのアリティを指定すると仮定しましょう。同様に、呼び出し可能なオブジェクトの戻り値の型を取得する方法もありますが、それもこの質問の範囲外です。戻り値の型が今のところだと仮定しましょうvoid

だから私たちは言いたいです、

call(F f, C v);

そしてそれは言うべきですf(v[0], v[1], ..., v[n-1])、どこfにアリティがありますかn


アプローチは次のとおりです。

template <unsigned int N, typename Functor, typename Container>
void call(Functor const & f, Container const & c)
{
    call_helper<N == 0, Functor, Container, N>::engage(f, c);
}

ヘルパーが必要です:

#include <functional>
#include <cassert>

template <bool Done, typename Functor, typename Container,
          unsigned int N, unsigned int ...I>
struct call_helper
{
    static void engage(Functor const & f, Container const & c)
    {
        call_helper<sizeof...(I) + 1 == N, Functor, Container,
                    N, I..., sizeof...(I)>::engage(f, c);
    }
};

template <typename Functor, typename Container,
          unsigned int N, unsigned int ...I>
struct call_helper<true, Functor, Container, N, I...>
{
    static void engage(Functor const & f, Container const & c)
    {
        assert(c.size() >= N);
        f(c[I]...);
    }
};

例:

#include <vector>
#include <iostream>

void f(int a, int b) { std::cout << "You said: " << a << ", " << b << "\n"; }

struct Func
{
    void operator()(int a, int b) const
    { std::cout << "Functor: " << a << "::" << b << "\n"; }
};

int main()
{
    std::vector<int> v { 20, 30 };
    call<2>(f, v);
    call<2>(Func(), v);
}

注:より高度なバージョンでは、呼び出し可能なオブジェクトのアリティをいくつかのテンプレート機構で推測し、戻り値の型も推測します。ただし、これが機能するには、無料の関数とさまざまな CV 修飾クラス メンバー関数に対していくつかの特殊化が必要になるため、この質問には大きすぎます。

于 2012-11-27T23:37:56.090 に答える
4

このようなことは、(メンバー) 関数ポインターでは簡単に可能ですが、潜在的にオーバーロードされた を持つファンクターのoperator()場合、これは非常に困難になります。関数が受け取る引数の数を知る方法があると仮定した場合 (そしてコンテナーが実際にその数の要素を持っていると仮定した場合)、単にインデックス トリックstd::nextを使用してベクトルを引数リストに展開できます。begin()イテレータ:

#include <utility>
#include <iterator>

template<class F, class Args, unsigned... Is>
auto invoke(F&& f, Args& cont, seq<Is...>)
  -> decltype(std::forward<F>(f)(*std::next(cont.begin(), Is)...))
{
  return std::forward<F>(f)(*std::next(cont.begin(), Is)...);
}

template<unsigned ArgC, class F, class Args>
auto invoke(F&& f, Args& cont)
  -> decltype(invoke(std::forward<F>(f), cont, gen_seq<ArgC>{}))
{
  return invoke(std::forward<F>(f), cont, gen_seq<ArgC>{});
}

この実装は、ランダム アクセス コンテナに対しては非常にうまく機能しますが、フォワードおよび特に入力コンテナに対してはあまりうまく機能しません。これらをパフォーマンスの高い方法で機能させるために、展開されたステップごとにイテレータをインクリメントするルートに進むことを試みるかもしれませんが、問題に遭遇します: 関数への引数の評価順序が指定されていないため、おそらく引数を間違った順序で渡します。

幸いなことに、左から右への評価を強制する方法があります: リスト初期化構文です。ここで、引数を渡すために使用できるコンテキストが必要です。可能なコンテキストは、オブジェクトを構築し、コンストラクターを介して関数と引数を渡し、そこで関数を呼び出すことです。ただし、コンストラクターは値を返すことができないため、返された値を取得する機能は失われます。

私が考えたのは、正しい要素を指すiteratorの配列を作成し、それらが逆参照される 2 番目のステップでそれらを再度展開することです。

#include <utility>

template<class T> using Alias = T; // for temporary arrays

template<class F, class It, unsigned N, unsigned... Is>
auto invoke_2(F&& f, It (&&args)[N], seq<Is...>)
  -> decltype(std::forward<F>(f)(*args[Is]...))
{
  return std::forward<F>(f)(*args[Is]...);
}

template<class F, class Args, unsigned... Is>
auto invoke_1(F&& f, Args& cont, seq<Is...> s)
  -> decltype(invoke_2(std::forward<F>(f), std::declval<decltype(cont.begin())[sizeof...(Is)]>(), s))
{
  auto it = cont.begin();
  return invoke_2(std::forward<F>(f), Alias<decltype(it)[]>{(void(Is), ++it)...}, s);
}

template<unsigned ArgC, class F, class Args>
auto invoke(F&& f, Args& cont)
  -> decltype(invoke_1(std::forward<F>(f), cont, gen_seq<ArgC>{}))
{
  return invoke_1(std::forward<F>(f), cont, gen_seq<ArgC>{});
}

コードは GCC 4.7.2 に対してテストされ、宣伝どおりに動作します。


渡されるファンクターは s であると言ったので、std::functionそれらが取る引数の数を取得するのは非常に簡単です。

template<class F> struct function_arity;

// if you have the 'Signature' of a 'std::function' handy
template<class R, class... Args>
struct function_arity<R(Args...)>
  : std::integral_constant<std::size_t, sizeof...(Args)>{};

// if you only have the 'std::function' available
template<class R, class... Args>
struct function_arity<std::function<R(Args...)>>
  : function_arity<R(Args...)>{};

上記の作業function_arityから作成する必要さえないことに注意してください。invokestd::function

template<class R, class... Ts, class Args>
R invoke(std::function<R(Ts...)> const& f, Args& cont){
  return invoke_1(f, cont, gen_seq<sizeof...(Ts)>{})
}
于 2012-11-27T23:46:19.540 に答える
1

私はあなたが望むことをすることができました。最初に正しい戻り値の型を推測しないままにしておくと、説明するのが最も簡単です。後でそれを追加する方法を示します。

#include <vector>
#include <type_traits>

namespace {
  int f(int a, int b) { return 0; }
}

template <typename ...Args>
constexpr unsigned nb_args(int (*)(Args...)) {
  return sizeof...(Args);
}

template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V&, Args&& ...args)
 -> typename std::enable_if<sizeof...(Args) == nb_args(F()),void>::type
{
  f(std::forward<Args>(args)...);
}

template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V& v, Args&& ...args)
 -> typename std::enable_if<sizeof...(Args) < nb_args(F()),void>::type
{
  bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args)));
}

int main() {
  bind_vec(&f, std::vector<int>(), 1);
  return 0;
}

これには 2 つのバージョンがありますbind_vec。一方は、パラメーター パックが関数に適したサイズである場合に有効になります。まだ小さすぎる場合は、もう一方が有効になります。最初のバージョンはパラメーター パックを使用して呼び出しをディスパッチするだけですが、2 番目のバージョンは (パラメーター パックのサイズによって決定される) 次の要素を取得して再帰します。

型の推定を妨げないように、関数の戻り値の型に対して SFINAE が実行されますが、これは、関数について知る必要があるため、関数の後に実行する必要があることを意味しますF。関数ポインターを呼び出すために必要な引数の数を見つけるヘルパー関数があります。

decltype戻り値の型を推測するために、関数ポインターを使用することもできます。

#include <vector>
#include <type_traits>

namespace {
  int f(int a, int b) { return 0; }
}

template <typename ...Args>
constexpr unsigned nb_args(int (*)(Args...)) {
  return sizeof...(Args);
}

template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V&, Args&& ...args)
 -> typename std::enable_if<sizeof...(Args) == nb_args(F()),decltype(f(std::forward<Args>(args)...))>::type
{
  return f(std::forward<Args>(args)...);
}

template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V& v, Args&& ...args)
 -> typename std::enable_if<sizeof...(Args) < nb_args(F()),decltype(bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args))))>::type
{
  return bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args)));
}

int main() {
  bind_vec(&f, std::vector<int>(), 1);
  return 0;
}
于 2012-11-27T23:38:55.620 に答える