У меня есть следующий код, который считывает значения/параметры из указателя и вызывает функцию:
#include <iostream>
#include <functional>
#include <string>
#include <tuple>
template<typename T>
T Read(void*& ptr)
{
T result = *static_cast<T*>(ptr);
ptr = static_cast<T*>(ptr) + 1;
return result;
}
template<typename T>
void Write(void*& ptr, const T &value)
{
*static_cast<T*>(ptr) = value;
ptr = static_cast<T*>(ptr) + 1;
}
template<typename R, typename... Args>
R Call(void* arguments, R (*func)(Args...))
{
//args = [c, b, a] somehow..?
auto args = std::make_tuple(Read<std::decay_t<Args>>(arguments)...);
return std::apply(func, args);
}
void func_one(int a, int b, int c)
{
std::cout<<"a: "<<a<<" b: "<<b<<" c: "<<c<<"\n";
}
int main()
{
int a = 1024;
int b = 2048;
int c = 3072;
int* args = new int[3];
void* temp = args;
Write(temp, a);
Write(temp, b);
Write(temp, c);
Call(args, func_one);
delete[] args;
return 0;
}
Однако, если я это сделаю:
auto args = std::tuple<Args...>();
std::apply([&arguments](auto&... args) {((args = Read<std::decay_t<decltype(args)>>(arguments)), ...);}, args);
Тогда args = [a, b, c]
. Это в правильном порядке. В последнем коде я использовал оператор запятой для чтения каждого значения и присвоения его кортежу.
Я пробовал: std::invoke(func, Read<std::decay_t<Args>>(arguments)...);
Что приводит к обратному порядку аргументов.
Итак. В чем разница между двумя ниже:
Read<Args>(arguments)... //tuple is reversed - a: 3072 b: 2048 c: 1024
//and
(Read<Args>(arguments)), ...) //tuple is in the right order - a: 1024 b: 2048 c: 3072
И почему первый создает кортеж в обратном порядке?
Есть ли лучший способ сделать это, используя std::apply
, чтобы получить правильный порядок?
gcc этого не делает, но clang выводит версию make_tuple в правильном порядке... может быть, дело в последовательности? 🤔
Я тоже использую Clang, и это в правильном порядке для меня.
@Brandon: Как вы, возможно, уже знаете, порядок оценки аргументов не указан. Я просто немного осмотрелся и не смог найти, как это взаимодействует с расширением пакета параметров, поэтому я оставлю это кому-то другому. Тем не менее, здесь это тема, в которой у кого-то, кажется (возможно), есть похожая проблема. В любом случае порядок гарантируется в других контекстах (например, при инициализации списка).
@scg Да. Первый, кроме, выводит неправильный порядок :D coliru.stacked-crooked.com/a/20719ce0ccae571d Я знал о порядке аргументов, но не подумал об этом, когда писал этот код для вариативных шаблонов. Я полагал, что они будут просто в том порядке, в котором они написаны и упорядочены ...
, чтобы означать «следующий» набор аргументов. Думаю, я очень сильно ошибался.
Аргументы в выражении вызова функции располагаются в неопределенной последовательности ([expr.call]/8 ). То есть при вызове std::make_tuple(Read<int>(arguments), Read<int>(arguments), Read<int>(arguments))
в вашем коде вызовы Read<int>(arguments)
происходят в любом порядке, в котором реализация хочет их выполнить, а не в том порядке, в котором они написаны. Это не неопределенное поведение, поскольку между вызовами есть точки последовательности, и поэтому вы не пытаетесь изменить arguments
несколько раз «одновременно». Просто программа определена стандартом как имеющая шесть возможных вариантов поведения (разные порядки выполнения вызовов), и она не указывает, какой из них происходит (или даже то, что она согласуется между компиляциями/запусками/вызовами в одной и той же реализации). !). Обратите внимание, что тот факт, что задействован пакет параметров, ничего не меняет: расширение пакета обрабатывается путем простого «подключения» расширенного синтаксиса вместо расширения, а затем обрабатывается в соответствии с правилами, отличными от пакета ( [temp.variadic ]/8).
Когда вы используете «фиксированную» версию, код расширяется, чтобы в основном содержать
arg1 = Read<int>(arguments), arg2 = Read<int>(arguments), arg3 = Read<int>(arguments);
где ,
теперь является оператором запятой и не является частью синтаксиса вызова функции. Операнды оператора запятой строго упорядочены слева направо.
Честно говоря, я не знаю более простого способа получить правильный порядок, чем то, что вы сделали. На самом деле, я думаю, у нас сложилась неприятная ситуация: если выражения-аргументы вызова функции имеют побочные эффекты, которые должны быть упорядочены в определенном порядке, то нет возможности инициализировать параметры функции непосредственно из выражений-аргументов (без копирования или двигаться). Конечно, здесь это не проблема. Тем не менее, ваша «фиксированная» версия предъявляет ненужные требования, чтобы задействованные типы также были конструируемыми по умолчанию и назначаемыми, что не обязательно. Обратите внимание, что по какой-то богом забытой причине, если вы используете фигурные скобки (инициализация списка) для вызова конструктора класса (в нашем случае, std::tuple
), то оценки выражений аргументов будут упорядочены слева направо, гарантированно ([dcl.init.list]/4).
template<typename R, typename... Args>
R Call(void *arguments, R (*func)(Args...)) {
std::tuple<std::decay_t<Args>...> args{Read<std::decay_t<Args>>(arguments)...};
return std::apply(func, std::move(args));
}
Аналогов для обычных вызовов функций я не знаю, так что ходы все равно происходят, а это печально (хотя, надеюсь, вы не пишете типы с дорогими ходами!).
Кроме того, я уверен, что вы уже это знаете, но обратите внимание, что вся эта история с void*
, кастингом и всем остальным кажется ужасно небезопасной. В частности, даже если предположить, что все вызывающие функции делают это "правильно", сами функции не обрабатывают выравнивание правильно (или вообще...).
Вы говорите, что это первый фрагмент кода в вашем сообщении, который выводится в неправильном порядке?