Переработка переменных аргументов из массива void** в C++

Я работаю над проектом, в котором мне нужно преобразовать переменные аргументы в массив 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])

Выбрасывать информацию о типе, а затем пытаться вернуть ее — плохая идея. void* почти никогда не бывает правильным в C++.

Pete Becker 09.06.2024 21:42

чтобы это работало, оно должно быть похоже на то, что делает std::function \ std::bind... выражение сгиба выглядит неправильно, все аргументы имеют разные типы..

Swift - Friday Pie 09.06.2024 21:50

Выбрасывать информацию о типе, а затем пытаться вернуть ее — плохая идея. void* почти никогда не бывает правильным в C++. Каковы ваши фактические требования (а не текущая техника реализации)?

Pete Becker 09.06.2024 21:51
helper совершенно излишне. tuple конструктор работает эффективно (с C++17 CTAD). В более старых версиях стандарта make_tuple помогает. Лямбда в вызове apply также является пустой тратой нажатий клавиш; reinterpreted_func делает свое дело: std::apply(reinterpreted_func, arg_tuple); наконец-то я не вижу смысла в вашей forwarder функции и всей этой reinterpret_cast гимнастике; почему бы просто не использовать std::invoke?
Red.Wave 10.06.2024 09:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
91
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В этой строке

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

Другие вопросы по теме