Как я могу выполнить итерацию по списку аргументов упакованного вариадического шаблона?
Я пытаюсь найти метод для перебора списка аргументов шаблона пакета variadic. Теперь, как и во всех итерациях, вам нужен какой-то метод определения количества аргументов в упакованном списке и, что более важно, как индивидуально получать данные из упакованного списка аргументов.
Основная идея состоит в том, чтобы перебрать список, сохранить все данные типа 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
выглядит очень похоже на питона. это тоже работает, но единственная проблема в том, как вы вызываете эту функцию. Вот быстрый пример:
foo(arg(1000));
Мне необходимо создать новый любой тип, который будет очень эстетичным, но это не значит, что эти макросы тоже не являются. Помимо этого, я просто хочу сделать выбор: foo(1000);
Я знаю, что это можно сделать, мне просто нужен какой-то итерационный метод, или, что более важно, какой-то метод std::get для упакованных списков аргументов шаблонов. Что, я уверен, можно сделать.
Также отметим, что я хорошо знаю, что это не совсем дружественно к типу, так как я поддерживаю только int,float,char* и это нормально для меня. Мне больше ничего не нужно, и я добавлю проверки, чтобы использовать type_traits для проверки того, что переданные аргументы действительно являются правильными, чтобы вызвать ошибку времени компиляции, если данные неверны. Это чисто не проблема. Мне также не нужна поддержка для чего-то другого, кроме этих типов POD.
Было бы очень признательно, если бы я мог получить некоторую конструктивную помощь, в отличие от аргументов о моем чисто нелогичном и глупом использовании макросов и POD-типов. Я хорошо знаю, насколько хрупок и сломан код. Это эксперимент, и я могу позже исправить проблемы с данными, не относящимися к POD, и сделать их более безопасными и удобными в использовании.
Спасибо за ваше понимание, и я с нетерпением жду помощи.
7 ответов
Если вы хотите обернуть аргументы в 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-му аргументу в шаблонной функции с переменным числом аргументов и применить функцию к каждому аргументу, что может быть лучшим способом сделать то, что вы хотите достичь.
Это действительно улучшение ответа JojOatXGME (я понял это, поигравшись с их ответом). Это лучше всего сработало для меня для входов смешанного типа без использования внешних функций (C++17):
#include <iostream>
template <typename ... T>
void Foo (const T && ... inputs)
{
int i = 0;
auto loop = [&] (auto && input)
{
// Do things in your "loop" lambda
++i;
std::cout << "input " << i << " = " << input << std::endl;
};
(loop(inputs), ...);
}
int main()
{
Foo(2, 3, 4u, (int64_t) 9, 'a', 2.3);
return 0;
}
Выходы для g++ -std=c++17
:
вход 1 = 2
входа 2 = 3
входа 3 = 4
входа 4 = 9
вход 5 =
вход 6 = 2,3
Если вы хотите что-то вроде условия прерывания или возврата внутри вашего "цикла", то вот способ сделать это:
struct BREAK {};
template <typename ... T>
bool Foo (const T && ... inputs)
{
int i = 0;
auto loop = [&] (auto && input)
{
// Do things in your "loop" lambda
++i;
std::cout << "input " << i << " = " << input << std::endl;
// some conditional breaks / returns
if (input == 9)
throw BREAK();
if (input < 0)
throw false;
if (input == 'a')
throw true;
};
try
{
(loop(inputs), ...);
}
catch (BREAK) {}
catch (bool return_val)
{
return return_val;
}
// more post-loop code
return true;
}
Диапазон на основе циклов замечательны:
#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
Вы можете создать его контейнер, инициализируя его своим пакетом параметров между {}. Пока тип params... является однородным или, по крайней мере, конвертируемым в тип элемента вашего контейнера, он будет работать. (протестировано с g++ 4.6.1)
#include <array>
template <class... Params>
void f(Params... params) {
std::array<int, sizeof...(params)> list = {params...};
}
В данный момент для него нет особой функции, но есть некоторые обходные пути, которые вы можете использовать.
Использование списка инициализации
Один обходной путь использует тот факт, что подвыражения списков инициализации оцениваются по порядку. 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...);
}
Использование рекурсии
Другая возможность - использовать рекурсию. Вот небольшой пример, который определяет аналогичную функцию 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...);
}
Это не то, как обычно используют шаблоны 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);
разрешается в (а), так как первый аргумент являетсяint
this->push(args...)
расширен доthis->push(3.2f, 4, 5, 4.2f);
, который разрешается в (б) в качестве первого аргумента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);
, который разрешается в (б) в качестве первого аргументаfloat
this->push(args...)
расширен доthis->push();
, который разрешается в (c), так как аргумент отсутствует, что приводит к прекращению рекурсии
Таким образом:
- Добавление другого типа для обработки так же просто, как добавление еще одной перегрузки, изменение первого типа (например,
std::string const&
) - Если передан совершенно другой тип (скажем,
Foo
), то перегрузка не может быть выбрана, что приводит к ошибке во время компиляции.
Одно предупреждение: автоматическое преобразование означает double
выбрал бы перегрузку (б) и short
выбрал бы перегрузку (а). Если это нежелательно, необходимо ввести 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.
Вы не можете повторить, но вы можете выполнить повтор по списку. Посмотрите пример printf() в Википедии: http://en.wikipedia.org/wiki/C++0x
#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
Вы можете использовать несколько шаблонов variadic, это немного грязно, но это работает и легко понять. У вас просто есть функция с шаблоном 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...);
}
Код не проверен.