web-dev-qa-db-ja.com

パックされた可変引数テンプレート引数リストを反復処理するにはどうすればよいですか?

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

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

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

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

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

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

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

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

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

あなたの理解に感謝し、私は助けることを楽しみにしています。

49
graphitemaster

引数を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番目の引数にアクセスし、各引数に関数を適用する関数を作成することは可能です。

32
UncleBens

これは通常、Variadicテンプレートを使用する方法ではありません。

言語規則に従って、可変個のパックの反復は不可能なので、再帰に向かう必要があります。

_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)最初の引数がintであるために解決されます
  • this->Push(args...)this->Push(3.2f, 4, 5, 4.2f);に展開され、最初の引数がfloatであるため(b)に解決されます
  • this->Push(args...)this->Push(4, 5, 4.2f);に展開され、最初の引数がintであるため(a)に解決されます
  • this->Push(args...)this->Push(5, 4.2f);に展開され、最初の引数がintであるため(a)に解決されます
  • this->Push(args...)this->Push(4.2f);に展開され、最初の引数がfloatであるため(b)に解決されます
  • this->Push(args...)this->Push();に展開され、引数がないため(c)に解決され、再帰が終了します

したがって:

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

注意点:自動変換では、doubleがオーバーロード(b)を選択し、shortがオーバーロード(a)を選択します。これが望ましくない場合は、SFINAEを導入する必要があります。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に保存、コピーなどが可能です。Variadicテンプレートを使用しなくても、本当に必要なものに似ています。

20
Matthieu M.

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

#include <array>

template <class... Params>
void f(Params... params) {
    std::array<int, sizeof...(params)> list = {params...};
}
18
karel

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

初期化リストを使用する

回避策の1つは、 初期化リスト の部分式が順番に評価されるという事実を使用しています。 int a[] = {get1(), get2()}は_get1_を実行する前に_get2_を実行します。多分 fold expression は将来同様のテクニックに便利になるでしょう。すべての引数で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)...};
}
_

ジェネリック lambdas (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...);
}
_

再帰を使用する

別の可能性は、再帰を使用することです。上記と同様の関数_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...);
}
_
10
JojOatXGME

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++"));
}

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

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
6
OMGtechy

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

3
chmeee

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

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

}

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

0
user3911448