Шаблон Varadic для кортежа перевернут

У меня есть следующий код, который считывает значения/параметры из указателя и вызывает функцию:

#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, чтобы получить правильный порядок?

Вы говорите, что это первый фрагмент кода в вашем сообщении, который выводится в неправильном порядке?

scg 12.12.2020 06:29

gcc этого не делает, но clang выводит версию make_tuple в правильном порядке... может быть, дело в последовательности? 🤔

TrebledJ 12.12.2020 06:31

Я тоже использую Clang, и это в правильном порядке для меня.

scg 12.12.2020 06:32

@Brandon: Как вы, возможно, уже знаете, порядок оценки аргументов не указан. Я просто немного осмотрелся и не смог найти, как это взаимодействует с расширением пакета параметров, поэтому я оставлю это кому-то другому. Тем не менее, здесь это тема, в которой у кого-то, кажется (возможно), есть похожая проблема. В любом случае порядок гарантируется в других контекстах (например, при инициализации списка).

scg 12.12.2020 06:43

@scg Да. Первый, кроме, выводит неправильный порядок :D coliru.stacked-crooked.com/a/20719ce0ccae571d Я знал о порядке аргументов, но не подумал об этом, когда писал этот код для вариативных шаблонов. Я полагал, что они будут просто в том порядке, в котором они написаны и упорядочены ..., чтобы означать «следующий» набор аргументов. Думаю, я очень сильно ошибался.

Brandon 12.12.2020 06:56
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
5
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Аргументы в выражении вызова функции располагаются в неопределенной последовательности ([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*, кастингом и всем остальным кажется ужасно небезопасной. В частности, даже если предположить, что все вызывающие функции делают это "правильно", сами функции не обрабатывают выравнивание правильно (или вообще...).

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