Как я могу выполнить итерацию по списку аргументов упакованного вариадического шаблона? - PullRequest
47 голосов
/ 29 августа 2011

Я пытаюсь найти метод для перебора списка аргументов шаблона переменной пакета.Теперь, как и во всех итерациях, вам нужен какой-то метод определения количества аргументов в упакованном списке и, что более важно, как по отдельности получать данные из упакованного списка аргументов.

Общая идея состоит в том, чтобы перебиратьсписок, хранить все данные типа 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));

Мне необходимо создать новый любой тип, который будет высокоэстетичным, но это не значит, что эти макросы тоже не являются.Помимо этого, я просто хочу использовать опцию: foo (1000);

Я знаю, что это можно сделать, мне просто нужен какой-то итерационный метод или, что более важно, некоторый метод std :: get дляупакованные списки аргументов шаблонов.Что, я уверен, может быть сделано.

Также следует отметить, что я хорошо знаю, что это не совсем дружественно к типу, так как я поддерживаю только int, float, char *, и это нормально для меня.Мне больше ничего не нужно, и я добавлю проверки, чтобы использовать type_traits для проверки того, что переданные аргументы действительно являются правильными, чтобы вызвать ошибку времени компиляции, если данные неверны.Это чисто не проблема.Мне также не нужна поддержка для чего-либо другого, кроме этих типов POD.

Было бы весьма полезно, если бы я мог получить некоторую конструктивную помощь, в отличие от аргументов о моем чисто нелогичном и глупом использовании макросов и типов только POD.,Я хорошо знаю, насколько хрупок и сломан код.Это эксперимент с мерли, и я позже смогу устранить проблемы с данными, не относящимися к POD, и сделать их более безопасными и удобными в использовании.

Спасибо за ваше понимание, и я с нетерпением жду помощи.

Ответы [ 7 ]

32 голосов
/ 29 августа 2011

Если вы хотите обернуть аргументы в 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-му аргументу в функции шаблона переменной и применить функцию к каждому аргументу, что может быть лучшим способом сделать все, что вы хотите достичь.

20 голосов
/ 29 августа 2011

Это не то, как обычно используют шаблоны 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);, который разрешается в (b), поскольку первый аргумент является float
  • this->push(args...) расширен до this->push(4, 5, 4.2f);, который разрешается в (a), поскольку первый аргумент является int
  • this->push(args...) расширен до this->push(5, 4.2f);, который разрешается в (a), поскольку первый аргумент является int
  • this->push(args...) расширен до this->push(4.2f);, который разрешается в (b), так как первый аргумент является float
  • this->push(args...) расширен до this->push();, который разрешается в (c), так как нет аргументов, что завершает рекурсию

Таким образом:

  • Добавление другого типа для обработки так же просто, как добавление другой перегрузки, изменение первого типа (например, std::string const&)
  • Если передан совершенно другой тип (скажем, Foo), перегрузка не может быть выбрана, что приведет к ошибке во время компиляции.

Одно предупреждение: автоматическое преобразование означает, что double выберет перегрузку (b), а short выберет перегрузку (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, скопирован и т. Д. И выглядит очень похоже на то, что вам нужно, даже если он не использует шаблоны Variadic.

18 голосов
/ 13 февраля 2012

Вы можете создать его контейнер, инициализируя его своим пакетом параметров между {}.Пока тип params ... является однородным или, по крайней мере, конвертируемым в тип элемента вашего контейнера, он будет работать.(протестировано с g ++ 4.6.1)

#include <array>

template <class... Params>
void f(Params... params) {
    std::array<int, sizeof...(params)> list = {params...};
}
10 голосов
/ 14 мая 2016

В данный момент для него нет особой функции, но есть некоторые обходные пути, которые вы можете использовать.

Использование списка инициализации

Один обходной путь использует тот факт, что подвыражения списков инициализации оцениваются по порядку. 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)...};
}

Обратите внимание, что с помощью 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...);
}
5 голосов
/ 17 июня 2018

Диапазон для петель замечательный:

#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
3 голосов
/ 29 августа 2011

Вы не можете выполнять итерации, но можете выполнять повторение по списку.Проверьте пример printf () в Википедии: http://en.wikipedia.org/wiki/C++0x#Variadic_templates

0 голосов
/ 26 сентября 2017

Вы можете использовать несколько шаблонов переменных, это немного грязно, но работает и легко понять.У вас просто есть функция с шаблоном variadic, например, так:

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

}

Теперь, когда вы вызываете «function», «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...);

}

Код не проверен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...