Как сохранить переменное количество аргументов, используя переменные аргументы шаблона?

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

Я хотел бы написать это универсально и не зависеть от типов аргументов или числа.

Вот отрывок идеи с использованием шаблонов переменных c ++ 11:

template<class T, typename... Params>
class LazyEvaluation {
private:
// Function to be invoked later
T (*f)(Params...);
// Params for function f
Params... storedParams; // This line is not compilable!
bool evaluated;
T result;
public:
// Constructor remembers function pointer and parameters
LazyEvaluation(T (*f)(Params...),Params... params)
: f(f),
storedParams(params) //this line also cannot be compiled
{}
// Method which can be called later to evaluate stored function with stored arguments
operator T&() {
// if not evaluated then evaluate
if (! evaluated) {
result = f(storedParams...);
evaluated = true;
}
return result;
}
}

Я хотел бы, чтобы хотя бы публичный интерфейс этого класса был безопасным, если это возможно. Хотя получить эту работу хотя бы как-то важнее.

Мне удалось как-то сохранить переменное количество аргументов. Но я не смог передать их функции f. Я напишу это в ответах, но я бы хотел, чтобы вы подумали о своих собственных решениях, прежде чем увидите мою уродливую неработающую попытку.

Я пытаюсь скомпилировать приведенный выше код с помощью компилятора Microsoft Visual C ++, ноябрь 2012 г., CTP (v120_CTP_Nov2012), но было бы лучше, если бы существовало независимое от компилятора решение.

Спасибо

6

Решение

Вот как я пытался это решить:

Пакет параметров может быть рекурсивно расширен и каждый параметр сохранен. Функция store должна это делать. Он использует одну (дважды перегруженную) вспомогательную функцию.

template<typename T>
void storeHelperFunction(void*& memory, T last) {
*((T*)memory) = last;
memory = (void*)((char*)memory + sizeof(T));
}

template<typename T, typename... Params>
void storeHelperFunction(void*& memory, T first, Params... rest) {
storeHelperFunction(memory, first);
storeHelperFunction(memory, rest...);
}

template<typename... Params>
void store(void* memory, Params... args) {
// Copy of pointer to memory was done when passing it to this function
storeHelperFunction(memory, args...);
}

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

Указатель может указывать на некоторую динамически распределенную память или лучше структуру, размер которой равен sizeof...(Params),
Такая структура, которая имеет абсолютно любой требуемый размер, может быть построена с использованием шаблонного метапрограммирования:

template <int N>
struct allocatorStruct {
char byte1;
allocatorStruct<N-1> next;
};

template <>
struct allocatorStruct<1> {};

Я не уверен, что говорит стандарт или как другие компиляторы, кроме Microsoft, компилируют его. Но используя мой компилятор, sizeof (allocatorStruct) равен N для любого N, которое больше или равно 1.

следовательно allocatorStruct<sizeof...(Params)> имеет тот же размер, что и Params.

Другой способ создать что-то такого же размера, как Params, — это использовать тип char [sizeof...(Params)], Это имеет тот недостаток, что компилятор передает только указатель на этот массив, когда вы пытаетесь передать такой массив в качестве аргумента.
Вот почему лучше использовать allocatorStruct<sizeof...(Params)>,

А теперь основная идея:

При сохранении функции мы можем привести ее к: T (*)(allocatorStruct<sizeof...(Params)>),
При сохранении аргументов для функции мы можем сохранить их в структуре типа allocatorStruct<sizeof...(Params)>,

Размер аргументов одинаков. Несмотря на то, что указатель на функцию относится к типу функции, указанная функция будет правильно получать свои данные.

По крайней мере, я надеялся. В зависимости от соглашения о вызовах я ожидал, что переданные аргументы могут быть переупорядочены или неверны из-за разницы между сохранением аргументов слева направо и передачей справа налево. Но это был не тот случай. При использовании соглашения о вызовах __cdecl был передан только первый аргумент, а другой потерян. С другими соглашениями о вызовах программа перестала работать.

Я не тратил много времени на его отладку и поиск данных в памяти (в стеке). Это хотя бы правильный путь?

1

Другие решения

Просто используйте лямбда-выражение

// Some function.
int add(int a, int b) {
return a + b;
}

auto lazyFunc = [] { return add(1, 2); };

std::cout << lazyFunc() << std::endl; // Evaluate function and output result.

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

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

Функции передаются и хранятся с использованием std::function обертка, поэтому мне не нужно работать с необработанными указателями на функции (гадость).

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

template <typename T>
class LazyEvaluation {};

template <typename ReturnType, typename... Params>
class LazyEvaluation<ReturnType(Params...)> {
private:
std::function<ReturnType(Params...)> func_;
ReturnType result;
std::tuple<Params...> oldParams; // Contains the previous arguments.
public:
explicit LazyEvaluation(std::function<ReturnType(Params...)> func)
: func_(std::move(func)) {}
template <typename... Args>
ReturnType operator() (Args&&... args) {
auto newParams = std::make_tuple(std::forward<Args>(args)...);

// Check if new arguments.
if (newParams != oldParams) {
result = func_(std::forward<Args>(args)...);
oldParams = newParams;
std::cout << "Function evaluated" << std::endl;
}

std::cout << "Returned result" << std::endl;
return result;
}
};

int main() {
auto f = [] (int a, int b) {
return a + b;
};

// Specify function type as template parameter.
// E.g. ReturnType(Param1Type, Param2Type, ..., ParamNType)
LazyEvaluation<int(int, int)> ld(f);

std::cout << ld(1, 2) << std::endl;
std::cout << ld(1, 2) << std::endl;
std::cout << ld(3, 4) << std::endl;
}

Выход:

Function evaluated
Returned result
3
Returned result
3
Function evaluated
Returned result
7
1

Приведено стандартное оборудование для формирования пакетов с переменным индексом:

template <std::size_t... I> struct index_sequence {};
template <std::size_t N, std::size_t... I>
struct make_index_sequence : public make_index_sequence<N-1, N-1, I...> {};
template <std::size_t... I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};

и вызывать функции с неупакованными аргументами кортежа:

template <typename Function, typename... Types, std::size_t... I>
auto apply_(Function&& f, const std::tuple<Types...>& t, index_sequence<I...>)
-> decltype(std::forward<Function>(f)(std::get<I>(t)...)) {
return std::forward<Function>(f)(std::get<I>(t)...);
}

template <typename Function, typename... Types>
auto apply(Function&& f, const std::tuple<Types...>& t)
-> decltype(apply_(f, t, make_index_sequence<sizeof...(Types)>())) {
return apply_(f, t, make_index_sequence<sizeof...(Types)>());
}

Это довольно просто:

template<typename Function, typename... Params>
class LazyEvaluation {
private:
typedef decltype(std::declval<Function>()(std::declval<Params>()...)) result_type;
// Function to be invoked later
Function f;
// Params for function f
std::tuple<Params...> storedParams;
mutable bool evaluated;
union {
std::aligned_storage<sizeof(result_type)> space;
mutable result_type result;
};

// Method which can be called later to evaluate stored function with stored arguments
void evaluate() const {
// if not evaluated then evaluate
if (! evaluated) {
new (&result) result_type{apply(f, storedParams)};
evaluated = true;
}
}

public:
// Constructor remembers function pointer and parameters
LazyEvaluation(Function f, Params... params)
: f(std::move(f)),
storedParams(std::move(params)...),
evaluated(false)
{}
~LazyEvaluation() {
if (evaluated)
result.~result_type();
}

operator result_type&() {
evaluate();
return result;
}

operator const result_type& () const {
evaluate();
return result;
}
};

template <typename Function, typename... Params>
LazyEvaluation<Function, Params...>
make_lazy(Function&& f, Params&&... params) {
return {std::forward<Function>(f), std::forward<Params>(params)...};
}

Я использовал союз и размещение new сохранить результат оценки так, чтобы он не должен был быть конструируемым по умолчанию типом, а некоторые mutable трюки, чтобы const LazyEvaluator может быть преобразован так же как неконстантный экземпляр.

0
По вопросам рекламы [email protected]