可変個引数テンプレートの引数を保存する方法は?


89

後で使用するためにパラメータパックを何らかの方法で保存することは可能ですか?

template <typename... T>
class Action {
private:        
    std::function<void(T...)> f;
    T... args;  // <--- something like this
public:
    Action(std::function<void(T...)> f, T... args) : f(f), args(args) {}
    void act(){
        f(args);  // <--- such that this will be possible
    }
}

その後、:

void main(){
    Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);

    //...

    add.act();
}

3
はい; タプルを格納し、ある種のインデックスパックを使用して呼び出しを行います。
Kerrek SB 2013

回答:


67

ここで実行したいことを実行するには、テンプレート引数をタプルに格納する必要があります。

std::tuple<Ts...> args;

さらに、コンストラクターを少し変更する必要があります。特に、で初期化argsし、std::make_tupleパラメータリストでユニバーサル参照を許可します。

template <typename F, typename... Args>
Action(F&& func, Args&&... args)
    : f(std::forward<F>(func)),
      args(std::forward<Args>(args)...)
{}

さらに、次のようにシーケンスジェネレータを設定する必要があります。

namespace helper
{
    template <int... Is>
    struct index {};

    template <int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};

    template <int... Is>
    struct gen_seq<0, Is...> : index<Is...> {};
}

そして、そのようなジェネレーターを使用するという観点からメソッドを実装できます。

template <typename... Args, int... Is>
void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
    f(std::get<Is>(tup)...);
}

template <typename... Args>
void func(std::tuple<Args...>& tup)
{
    func(tup, helper::gen_seq<sizeof...(Args)>{});
}

void act()
{
   func(args);
}

そしてそれ!これで、クラスは次のようになります。

template <typename... Ts>
class Action
{
private:
    std::function<void (Ts...)> f;
    std::tuple<Ts...> args;
public:
    template <typename F, typename... Args>
    Action(F&& func, Args&&... args)
        : f(std::forward<F>(func)),
          args(std::forward<Args>(args)...)
    {}

    template <typename... Args, int... Is>
    void func(std::tuple<Args...>& tup, helper::index<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, helper::gen_seq<sizeof...(Args)>{});
    }

    void act()
    {
        func(args);
    }
};

これがColiruの完全なプログラムです。


更新:テンプレート引数の指定が不要なヘルパーメソッドは次のとおりです。

template <typename F, typename... Args>
Action<Args...> make_action(F&& f, Args&&... args)
{
    return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...);
}

int main()
{
    auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3);

    add.act();
}

また、ここに別のデモがあります。


1
あなたの答えでそれを少し拡張できますか?
エリックB

1
Ts ...はクラステンプレートパラメータであり、関数テンプレートパラメータではないため、Ts && ...は、パラメータパックで型の推定が行われないため、ユニバーサル参照のパックを定義しません。@jogojapanは、コンストラクターにユニバーサル参照を渡すことができるようにする正しい方法を示しています。
masrtis 2013

3
参照とオブジェクトの存続期間に注意してください! void print(const std::string&); std::string hello(); auto act = make_action(print, hello());良くないです。またはでstd::bind無効にしない限り、各引数のコピーを作成するの動作をお勧めします。std::refstd::cref
aschepler 2014

1
@jogojapanには、もっと簡潔で読みやすいソリューションがあると思います。
Tim Kuipers 2016年

1
@Riddickに変更args(std::make_tuple(std::forward<Args>(args)...))args(std::forward<Args>(args)...)ます。ところで、私はずっと前にこれを書いたので、関数をいくつかの引数にバインドする目的でこのコードを使用することはありませんでした。私はただ使うstd::invoke()std::apply()、最近は。
0x499602D2

23

std::bind(f,args...)これに使用できます。関数オブジェクトと各引数のコピーを後で使用するために格納する、移動可能でコピー可能なオブジェクトを生成します。

#include <iostream>
#include <utility>
#include <functional>

template <typename... T>
class Action {
public:

  using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...));

  template <typename... ConstrT>
  Action(std::function<void(T...)> f, ConstrT&&... args)
    : bind_(f,std::forward<ConstrT>(args)...)
  { }

  void act()
  { bind_(); }

private:
  bind_type bind_;
};

int main()
{
  Action<int,int> add([](int x, int y)
                      { std::cout << (x+y) << std::endl; },
                      3, 4);

  add.act();
  return 0;
}

これstd::bindは関数であり、呼び出した結果をデータメンバーとして保存する必要があることに注意してください。その結果のデータ型を予測するのは簡単ではないので(標準では正確に指定されていません)、コンパイル時にdecltypeとの組み合わせを使用してstd::declvalそのデータ型を計算します。Action::bind_type上記の定義を参照してください。

また、テンプレート化されたコンストラクターでユニバーサル参照をどのように使用したかに注目してください。これにより、クラステンプレートパラメータとT...正確に一致しない引数を渡すことができます(たとえば、一部への右辺値参照を使用Tすると、それらをそのままbind呼び出しに転送できます)。

最後の注意:引数を参照として格納する場合(渡す関数が引数を単に使用するのではなく変更できるようにするため)、を使用std::refして引数を参照オブジェクトでラップする必要があります。を渡すだけでT &、参照ではなく値のコピーが作成されます。

Coliruの操作コード


右辺値をバインドするのは危険ではありませんか?addがどこact()で呼び出されるかとは異なるスコープで定義されている場合、それらは無効になりませんか?コンストラクタは、取得すべきではないConstrT&... argsのではなくConstrT&&... args
Tim Kuipers 2016年

1
@Angelorf返信が遅くなってすみません。bind()?への呼び出しの右辺値を意味します。以来bind()コピーを作成する(または新規に作成したオブジェクトに移動すること)が保証されて、私は問題がある可能性がありますとは思いません。
jogojapan 2016年

@jogojapanクイックノート、MSVC17では、コンストラクター内の関数もbind_に転送する必要があります(つまり、bind_(std :: forward <std :: function <void(T ...)>>(f)、std :: forward < ConstrT>(args)...))
2017

1
初期化子でbind_(f, std::forward<ConstrT>(args)...)は、コンストラクターが実装定義されているため、標準に従って未定義の動作になります。bind_typeただし、コピーおよび/または移動構築可能であるように指定されているため、bind_{std::bind(f, std::forward<ConstrT>(args)...)}引き続き機能するはずです。
joshtch

4

この質問はC ++ 11日からのものでした。しかし、今検索​​結果でそれを見つけている人のために、いくつかの更新:

std::tupleメンバーは、まだ一般的に引数を格納するための簡単な方法です。(@jogojapanstd::bindと同様のソリューションは、特定の関数を呼び出すだけの場合にも機能しますが、他の方法で引数にアクセスしたり、引数を複数の関数に渡したりする場合などは機能しません)

C ++ 14以降、std::make_index_sequence<N>またはstd::index_sequence_for<Pack...>0x499602D2のソリューションにhelper::gen_seq<N>見られるツールを置き換えることができます

#include <utility>

template <typename... Ts>
class Action
{
    // ...
    template <typename... Args, std::size_t... Is>
    void func(std::tuple<Args...>& tup, std::index_sequence<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, std::index_sequence_for<Args...>{});
    }
    // ...
};

C ++ 17以降でstd::applyは、タプルの解凍を処理するために使用できます。

template <typename... Ts>
class Action
{
    // ...
    void act() {
        std::apply(f, args);
    }
};

これは、簡略化された実装を示す完全なC ++ 17プログラムです。またmake_action、の参照型を回避するように更新しましたtuple。これは、右辺値引数には常に悪かったし、左辺値引数にはかなり危険です。


3

XYの問題があると思います。コールサイトでラムダを使用できるのに、なぜパラメータパックを保存するのに苦労するのですか?すなわち、

#include <functional>
#include <iostream>

typedef std::function<void()> Action;

void callback(int n, const char* s) {
    std::cout << s << ": " << n << '\n';
}

int main() {
    Action a{[]{callback(13, "foo");}};
    a();
}

私のアプリケーションでは、アクションには実際にはすべて関連する3つの異なるファンクターがあり、それを含むクラスには3つのstd :: functionではなく1つのアクションが含まれるほうがよいため
Eric B
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.