67

パックの可変個引数テンプレートの引数リストを反復処理する方法を見つけようとしています。すべての反復と同様に、パックリストに含まれる引数の数を知るための何らかの方法が必要です。さらに重要なのは、パックされた引数リストからデータを個別に取得する方法です。

一般的な考え方は、リストを反復処理し、int型のすべてのデータをベクトルに格納し、char *型のすべてのデータをベクトルに格納し、float型のすべてのデータをベクトルに格納することです。このプロセスでは、引数が入った順序の個々の文字を格納する個別のベクトルも必要です。たとえば、push_back(a_float)を実行すると、単に格納するpush_back('f')も実行されます。データの順序を知るための個々の文字。ここでstd::stringを使用して、単に+=を使用することもできます。ベクトルは単なる例として使用されました。

さて、物事の設計方法は、悪意にもかかわらず、関数自体がマクロを使用して構築されることです。これは実験であるため、必須です。したがって、再帰呼び出しを使用することは文字通り不可能です。これは、これらすべてを格納する実際の実装がコンパイル時に拡張されるためです。マクロを元に戻すことはできません。

考えられるすべての試みにもかかわらず、私はまだこれを実際に行う方法を理解することに固執しています。その代わりに、型を作成し、その型をvaradicテンプレートに渡し、ベクトル内で展開して、それを単純に繰り返すという、より複雑な方法を使用しています。ただし、次のような関数を呼び出す必要はありません。

foo(arg(1), arg(2.0f), arg("three");

だから本当の問題は、どうすればそのようなことなしにできるのかということです。コードが実際に何をしているのかをよりよく理解できるように、現在使用している楽観的なアプローチを貼り付けました。

struct any {
  void do_i(int   e) { INT    = e; }
  void do_f(float e) { FLOAT  = e; }
  void do_s(char* e) { STRING = e; }

  int   INT;
  float FLOAT;
  char *STRING;
};


template<typename T> struct get        { T      operator()(const any& t) { return T();      } };
template<>           struct get<int>   { int    operator()(const any& t) { return t.INT;    } };
template<>           struct get<float> { float  operator()(const any& t) { return t.FLOAT;  } };
template<>           struct get<char*> { char*  operator()(const any& t) { return t.STRING; } };

#define def(name)                                  \
  template<typename... T>                          \
  auto name (T... argv) -> any {                   \
   std::initializer_list<any> argin = { argv... }; \
    std::vector<any> args = argin;
#define get(name,T)  get<T>()(args[name])
#define end }

any arg(int   a) { any arg; arg.INT    = a; return arg; }
any arg(float f) { any arg; arg.FLOAT  = f; return arg; }
any arg(char* s) { any arg; arg.STRING = s; return arg; }

これは厄介なことですが、これは純粋な実験であり、本番コードでは使用されません。それは純粋にアイデアです。それはおそらくもっと良い方法で行われる可能性があります。しかし、このシステムをどのように使用するかの例:

def(foo)
  int data = get(0, int);
  std::cout << data << std::endl;
end

Pythonによく似ています。それも機能しますが、唯一の問題はこの関数をどのように呼び出すかです。ここに簡単な例があります:

foo(arg(1000));

非常に美的である新しい任意のタイプを構築する必要がありますが、それはそれらのマクロもそうではないということではありません。要点はさておき、私は次のオプションを実行したいだけです:foo(1000);

私はそれができることを知っています、私はある種の反復メソッド、またはもっと重要なことに、パックされた可変個引数テンプレート引数リストのためのいくつかのstd::getメソッドが必要です。私はそれができると確信しています。

また、私はint、float、char *のみをサポートしているので、これが正確にタイプフレンドリーではないことをよく知っています。それは私にとっては問題ありません。他に何も必要ありません。type_traitsを使用して、渡された引数が実際に正しいものであることを検証するチェックを追加して、データが正しくない場合にコンパイル時エラーを生成します。これは純粋に問題ではありません。また、これらのPODタイプ以外のサポートも必要ありません。

マクロとPODのみのタイプの純粋に非論理的で愚かな使用についての議論とは対照的に、建設的な助けを得ることができれば非常に高く評価されます。私は、コードがいかに壊れやすく壊れているかをよく知っています。これは実験的なものであり、後で非PODデータの問題を修正して、よりタイプセーフで使いやすくすることができます。

ご理解のほどよろしくお願いいたします。お役に立てることを楽しみにしております。

4

9 に答える 9

37

引数をにラップするany場合は、次の設定を使用できます。また、any技術的にはクラスではありませんが、クラスをもう少し使いやすくしましたany

#include <vector>
#include <iostream>

struct any {
  enum type {Int, Float, String};
  any(int   e) { m_data.INT    = e; m_type = Int;}
  any(float e) { m_data.FLOAT  = e; m_type = Float;}
  any(char* e) { m_data.STRING = e; m_type = String;}
  type get_type() const { return m_type; }
  int get_int() const { return m_data.INT; }
  float get_float() const { return m_data.FLOAT; }
  char* get_string() const { return m_data.STRING; }
private:
  type m_type;
  union {
    int   INT;
    float FLOAT;
    char *STRING;
  } m_data;
};

template <class ...Args>
void foo_imp(const Args&... args)
{
    std::vector<any> vec = {args...};
    for (unsigned i = 0; i < vec.size(); ++i) {
        switch (vec[i].get_type()) {
            case any::Int: std::cout << vec[i].get_int() << '\n'; break;
            case any::Float: std::cout << vec[i].get_float() << '\n'; break;
            case any::String: std::cout << vec[i].get_string() << '\n'; break;
        }
    }
}

template <class ...Args>
void foo(Args... args)
{
    foo_imp(any(args)...);  //pass each arg to any constructor, and call foo_imp with resulting any objects
}

int main()
{
    char s[] = "Hello";
    foo(1, 3.4f, s);
}

ただし、可変個引数テンプレート関数のn番目の引数にアクセスし、各引数に関数を適用する関数を作成することは可能です。これは、達成したいことを行うためのより良い方法である可能性があります。

于 2011-08-29T16:30:45.117 に答える
25

入力がすべて同じタイプの場合は、OMGtechyのすばらしい答えを参照してください。

混合型の場合、呼び出し可能(この場合はラムダ)でfold式(で導入)を使用できます。c++17

#include <iostream>

template <typename ... Ts>
void Foo (Ts && ... multi_inputs)
{
    int i = 0;
    
    ([&] (auto & input)
    {
        // Do things in your "loop" lambda
    
        ++i;
        std::cout << "input " << i << " = " << input << std::endl;

    } (multi_inputs), ...);
}

int main ()
{
    Foo(2, 3, 4u, (int64_t) 9, 'a', 2.3);
}

ライブデモ

完全な転送が必要な場合は、次のように変更します。

(auto && input) // double "&" now

// ...

(std::forward<T>(multi_inputs)), ...);

ループにreturn/ sが必要な場合は、次の回避策があります。break

これらの後者の答えは正直なところコードの臭いですが、それが汎用であることを示しています。

于 2020-02-09T12:44:26.833 に答える
24

{}の間にパラメータパックを使用して初期化することで、コンテナを作成できます。params ...のタイプが同種であるか、少なくともコンテナの要素タイプに変換可能である限り、それは機能します。(g ++ 4.6.1でテスト済み)

#include <array>

template <class... Params>
void f(Params... params) {
    std::array<int, sizeof...(params)> list = {params...};
}
于 2012-02-13T14:15:11.927 に答える
22

これは、通常、可変個引数テンプレートを使用する方法ではなく、まったく使用しません。

言語規則に従って、可変個引数パックを反復処理することはできないため、再帰に向ける必要があります。

class Stock
{
public:
  bool isInt(size_t i) { return _indexes.at(i).first == Int; }
  int getInt(size_t i) { assert(isInt(i)); return _ints.at(_indexes.at(i).second); }

  // push (a)
  template <typename... Args>
  void push(int i, Args... args) {
    _indexes.push_back(std::make_pair(Int, _ints.size()));
    _ints.push_back(i);
    this->push(args...);
  }

  // push (b)
  template <typename... Args>
  void push(float f, Args... args) {
    _indexes.push_back(std::make_pair(Float, _floats.size()));
    _floats.push_back(f);
    this->push(args...);
  }

private:
  // push (c)
  void push() {}

  enum Type { Int, Float; };
  typedef size_t Index;

  std::vector<std::pair<Type,Index>> _indexes;
  std::vector<int> _ints;
  std::vector<float> _floats;
};

例(実際の動作)では、次のようになりますStock stock;

  • stock.push(1, 3.2f, 4, 5, 4.2f);最初の引数は(a)であるため、は(a)に解決されます。int
  • this->push(args...)はに展開されthis->push(3.2f, 4, 5, 4.2f);ます。これは、最初の引数がaであるため、(b)に解決されます。float
  • this->push(args...)はに展開されthis->push(4, 5, 4.2f);ます。これは、最初の引数がint
  • this->push(args...)はに展開されthis->push(5, 4.2f);ます。これは、最初の引数がint
  • this->push(args...)はに展開されthis->push(4.2f);ます。これは、最初の引数がaであるため、(b)に解決されます。float
  • this->push(args...)はに展開されthis->push();ますが、引数がないため(c)に解決され、再帰が終了します。

したがって:

  • 処理する別のタイプの追加は、別のオーバーロードを追加して最初のタイプを変更するのと同じくらい簡単です(たとえばstd::string const&
  • 完全に異なるタイプが渡された場合(たとえばFoo)、オーバーロードを選択できず、コンパイル時エラーが発生します。

注意点:自動変換とは、adoubleが過負荷(b)をshort選択し、aが過負荷(a)を選択することを意味します。これが望ましくない場合は、SFINAEを導入する必要があります。これにより、メソッドが少し複雑になります(少なくとも、それらのシグネチャ)。例:

template <typename T, typename... Args>
typename std::enable_if<is_int<T>::value>::type push(T i, Args... args);

どこis_intにあるでしょう:

template <typename T> struct is_int { static bool constexpr value = false; };
template <> struct is_int<int> { static bool constexpr value = true; };

ただし、別の代替手段は、バリアント型を検討することです。例えば:

typedef boost::variant<int, float, std::string> Variant;

すでに存在し、すべてのユーティリティで、保存したりvector、コピーしたりすることができます。可変個引数テンプレートを使用していなくても、必要なものと非常によく似ています。

于 2011-08-29T13:39:24.000 に答える
20

現在、特定の機能はありませんが、使用できるいくつかの回避策があります。

初期化リストの使用

1つの回避策は、初期化リストの部分式が順番に評価されるという事実を使用します。int a[] = {get1(), get2()}実行するget1前に実行されget2ます。将来的には、折り畳み式が同様のテクニックに役立つかもしれません。すべての引数を呼び出すdo()には、次のようなことを行うことができます。

template <class... Args>
void doSomething(Args... args) {
    int x[] = {args.do()...};
}

ただし、これは、do()がを返す場合にのみ機能しintます。コンマ演算子を使用して、適切な値を返さない操作をサポートできます。

template <class... Args>
void doSomething(Args... args) {
    int x[] = {(args.do(), 0)...};
}

より複雑なことを行うために、それらを別の関数に入れることができます。

template <class Arg>
void process(Arg arg, int &someOtherData) {
    // You can do something with arg here.
}

template <class... Args>
void doSomething(Args... args) {
    int someOtherData;
    int x[] = {(process(args, someOtherData), 0)...};
}

汎用ラムダ(C ++ 14)を使用すると、この定型文を実行する関数を定義できることに注意してください。

template <class F, class... Args>
void do_for(F f, Args... args) {
    int x[] = {(f(args), 0)...};
}

template <class... Args>
void doSomething(Args... args) {
    do_for([&](auto arg) {
        // You can do something with arg here.
    }, args...);
}

再帰を使用する

もう1つの可能性は、再帰を使用することです。上記と同様の関数を定義する小さな例を次に示しますdo_for

template <class F, class First, class... Rest>
void do_for(F f, First first, Rest... rest) {
    f(first);
    do_for(f, rest...);
}
template <class F>
void do_for(F f) {
    // Parameter pack is empty.
}

template <class... Args>
void doSomething(Args... args) {
    do_for([&](auto arg) {
        // You can do something with arg here.
    }, args...);
}
于 2016-05-14T13:33:44.417 に答える
20

範囲ベースのforループは素晴らしいです:

#include <iostream>
#include <any>

template <typename... Things>
void printVariadic(Things... things) {
    for(const auto p : {things...}) {
        std::cout << p.type().name() << std::endl;
    }
}

int main() {
    printVariadic(std::any(42), std::any('?'), std::any("C++"));
}

私にとって、これは出力を生成します:

i
c
PKc

を使用しない例を次に示しstd::anyます。これは、次のことをよく知らない人にとっては理解しやすいかもしれませんstd::type_info

#include <iostream>

template <typename... Things>
void printVariadic(Things... things) {
    for(const auto p : {things...}) {
        std::cout << p << std::endl;
    }
}

int main() {
    printVariadic(1, 2, 3);
}

ご想像のとおり、これにより以下が生成されます。

1
2
3
于 2018-06-16T23:41:34.237 に答える
4

繰り返すことはできませんが、リストを繰り返すことはできます。ウィキペディアのprintf()の例を確認してください:http: //en.wikipedia.org/wiki/C++0x#Variadic_templates

于 2011-08-29T13:22:14.760 に答える
1

複数の可変個引数テンプレートを使用できます。これは少し面倒ですが、機能し、理解しやすいです。次のような可変個引数テンプレートを使用した関数があります。

template <typename ...ArgsType >
void function(ArgsType... Args){
     helperFunction(Args...);
}

そして、ヘルパーは次のように機能します。

void helperFunction() {}

template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {

     //do what you want with t
    function(Args...);

}

これで、「関数」を呼び出すと、「helperFunction」が呼び出され、最初に渡されたパラメーターが残りのパラメーターから分離されます。この変数を使用して、別の関数(または何か)を呼び出すことができます。次に、変数がなくなるまで「関数」が何度も呼び出されます。「関数」の前にhelperClassを宣言しなければならない場合があることに注意してください。

最終的なコードは次のようになります。

void helperFunction();

template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args);

template <typename ...ArgsType >
void function(ArgsType... Args){
     helperFunction(Args...);
}

void helperFunction() {}

template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {

     //do what you want with t
    function(Args...);

}

コードはテストされていません。

于 2017-09-26T20:18:46.430 に答える
0
#include <iostream>    

template <typename Fun>
void iteratePack(const Fun&) {}

template <typename Fun, typename Arg, typename ... Args>
void iteratePack(const Fun &fun, Arg &&arg, Args&& ... args)
{
    fun(std::forward<Arg>(arg));
    iteratePack(fun, std::forward<Args>(args)...);
}

template <typename ... Args>
void test(const Args& ... args)
{
    iteratePack([&](auto &arg)
    {
        std::cout << arg << std::endl;
    },
    args...);
}

int main()
{
    test(20, "hello", 40);

    return 0;
}

出力:

20
hello
40
于 2020-09-22T13:33:49.810 に答える