Я работаю над проектом, в котором мне нужно преобразовать переменные аргументы в массив void**
, передать их в лямбда-функцию, а затем правильно привести их обратно к исходным типам для использования в пользовательской функции. Хотя я могу успешно создать массив void**
, у меня возникают проблемы с правильным расширением и приведением переменных аргументов обратно к их исходным типам внутри лямбда-функции.
Пользовательская функция предназначена для обработки этих аргументов, но выдаются неправильные значения, что указывает на проблему с тем, как я обрабатываю массив void**
и последующие приведения.
Важно отметить, что это предназначено для использования с любой функцией, не ограничиваясь только показанными типами, что отличается от других примеров, которые я нашел при переполнении стека.
Ниже приведен минимальный воспроизводимый пример моего подхода. Он демонстрирует:
void*
.Я разделил расширение параметров в целях отладки с помощью функции helper(...)
.
Может ли кто-нибудь помочь мне определить, почему аргументы не возвращаются к исходным типам правильно и как правильно обработать это преобразование?
#include <iostream>
#include <tuple>
// Implementation of the forwarder function
void raw_forwarder(void (*f)(void **), void **args) {
f(args);
// some other procedure in here
}
// Base case helper function
template<typename T>
static auto helper(T &arg) {
std::cout << "helper 2 - val:\t" << arg << "\tat: " << &arg << std::endl;
return std::make_tuple(arg);
}
// Recursive helper function
template<typename T, typename... Args>
static auto helper(T &arg, Args &... args) {
std::cout << "helper 1 - val:\t" << arg << "\tat: " << &arg << std::endl;
return std::tuple_cat(std::make_tuple(arg), helper(args...));
}
template<typename F, typename... Args>
static void forwarder(F &f, Args &... args) {
// Create an array of void* to hold the addresses of the arguments
void *argArray[] = {reinterpret_cast<void *>(&f), reinterpret_cast<void *>(&args)...};
// Call the raw forwarder with the function and arguments
raw_forwarder([](void **args_raw) {
// Retrieve the function pointer
F &reinterpreted_func = *reinterpret_cast<F *>(args_raw[0]);
// Create a tuple of argument pointers using the helper functions
auto arg_tuple = helper(*reinterpret_cast<Args *>(args_raw[1])...);
std::cout << std::endl;
// Apply the function to the dereferenced arguments
std::apply([&reinterpreted_func](auto &&... unpacked_args) { reinterpreted_func(unpacked_args...); }, arg_tuple);
},
argArray);
}
void custom_function(int val1, float val2, double val3) {
std::cout << "val1 = " << val1 << std::endl;
std::cout << "val2 = " << val2 << std::endl;
std::cout << "val3 = " << val3 << std::endl;
}
int main() {
int val1 = 7;
float val2 = 5.5f;
double val3 = 10.0;
helper(val1, val2, val3); // this works
std::cout << std::endl;
forwarder(custom_function, val1, val2, val3);
return 0;
}
Вырезанный из вышеизложенного вывод (показан ниже). Я заметил, что при расширении указателей проблема заключается в том, что для всех параметров передается одно и то же значение указателя. Это приводит к использованию неверных значений в пользовательской функции. Как я могу правильно расширить и привести аргументы с вариациями, чтобы избежать этой проблемы?
helper 1 - val: 7 at: 0x4fcd9ff84c
helper 1 - val: 5.5 at: 0x4fcd9ff848
helper 2 - val: 10 at: 0x4fcd9ff840
helper 1 - val: 7 at: 0x4fcd9ff84c
helper 1 - val: 9.80909e-45 at: 0x4fcd9ff84c
helper 2 - val: 3.45846e-323 at: 0x4fcd9ff84c
val1 = 7
val2 = 9.80909e-45
val3 = 3.45846e-323
Для справки: в режиме отладки, если посмотреть на args_raw
, как показано ниже, я вижу, что ячейки действительно указывают на фактические значения.
*reinterpret_cast<int*>(args_raw[1])
*reinterpret_cast<float*>(args_raw[2])
*reinterpret_cast<double*>(args_raw[3])
чтобы это работало, оно должно быть похоже на то, что делает std::function
\ std::bind
... выражение сгиба выглядит неправильно, все аргументы имеют разные типы..
Выбрасывать информацию о типе, а затем пытаться вернуть ее — плохая идея. void*
почти никогда не бывает правильным в C++. Каковы ваши фактические требования (а не текущая техника реализации)?
helper
совершенно излишне. tuple
конструктор работает эффективно (с C++17 CTAD). В более старых версиях стандарта make_tuple
помогает. Лямбда в вызове apply
также является пустой тратой нажатий клавиш; reinterpreted_func
делает свое дело: std::apply(reinterpreted_func, arg_tuple);
наконец-то я не вижу смысла в вашей forwarder
функции и всей этой reinterpret_cast
гимнастике; почему бы просто не использовать std::invoke
?
В этой строке
auto arg_tuple = helper(*reinterpret_cast<Args*>(args_raw[1])...);
индекс массива должен увеличиваться вместе с расширением пакета.
Минимальное исправление может выглядеть так:
template<typename F, std::size_t... Is, typename... Args>
void forwarder_impl(F& f, std::index_sequence<Is...>, Args&... args) {
// ...
auto arg_tuple = helper(*reinterpret_cast<Args*>(args_raw[Is + 1])...);
// ...
}
template<typename F, typename... Args>
void forwarder(F& f, Args&... args) {
forwarder_impl(f, std::index_sequence_for<Args...>(), args...);
}
Здесь мы ввели один уровень косвенности для введения пакета индексов Is...
, который затем расширяется вместе с Args...
.
Если вы избавитесь от helper
, вы можете упростить это до:
template<typename F, typename... Args>
void forwarder(F& f, Args&... args) {
// ...
std::size_t i = 1;
std::tuple arg_tuple{*reinterpret_cast<Args*>(args_raw[i++])...};
// ...
}
Здесь мы используем тот факт, что внутри фигурных скобок элементы списка оцениваются в том порядке, в котором они появляются в списке (согласно [dcl.init.list/4]
).
Выбрасывать информацию о типе, а затем пытаться вернуть ее — плохая идея.
void*
почти никогда не бывает правильным в C++.