C++ 14 шаблонов Variadic для вызова различных методов с помощью перечисления

Я хочу иметь 2 метода: один — SetVariable, а другой — GetVariable в MyClass. оба получат перечисление и аргументы, аргументы могут быть более одного и более одного типа и согласно перечислению будет вызван соответствующий частный метод Я хочу вызвать этот метод из другого класса под названием OtherClass. (это не настоящие имена, но для объяснения это так) Я использую триаду с

template <typename... Args>
    bool SetVariable(const int& variable, Args&&... args)
    {
        auto returnValue = false;
        switch (variable)
        {
        case 1:
        {
            returnValue = Function1(std::forward<Args>(args)...);
            break;
        }
        case 2:
        {
            returnValue = Function2(std::forward<Args>(args)...);
            break;
        }

        default:
            break;
        }

        return returnValue;
    }

это обе функции1 и 2:

Function1( const int & axis_, const double& velocity_ )
Function2( std::vector<int>& chameleonFirmwareVec_ )

вот как я вызываю SetVariable из другого класса

bool Core::OtherClass::Function1Call( const int & axis_, const double& velocity_ )
{
    return MyClass.SetVariable(1, axis_, velocity_);
}

ошибка здесь Function2': функция не принимает 2 аргумента и я не вызываю это событие, если у меня более одного случая, ошибка ампера

пожалуйста, опубликуйте минимально воспроизводимый пример.

463035818_is_not_an_ai 14.08.2024 10:42
returnValue = Function2(std::forward<Args>(args)...); должен быть действительным, даже если ветку не берут.
463035818_is_not_an_ai 14.08.2024 10:48

Сравните это с обычными if и else, где оба кода ветки должны быть действительными.

user12002570 14.08.2024 10:54

«Я триаду с ChatGPT...» ИИ не является хорошим/надежным источником проблем с кодированием. книга намного лучше.

user12002570 14.08.2024 10:55
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
84
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

но проблема в том, что я получил неправильный номер аргумента.

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

Если решение о выборе ветки может быть принято во время компиляции, вы можете использовать constexpr if.

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

Проблема в том, что здесь значение перечисления (variable) определяется во время выполнения, тогда как тип аргументов (Args) определяется во время компиляции.

Таким образом, при вызове SetVariable(1, axis_, velocity_) компилятор генерирует экземпляр шаблона для функции SetVariable(int, double), и эта функция содержит вызов Function2(int, double), которого не существует. И это вызывает ошибку компиляции.

Два способа избежать этого - либо сделать типы аргументов также определяемыми во время выполнения, либо сделать перечисление определяемым во время компиляции.


Чтобы тип аргументов определялся во время выполнения, можно было бы сделать это следующим образом:

using AxisVelocity = std::pair<int, double>;
void SetVariable(
   int variable,
   const std::variant<
       AxisVelocity,
       std::vector<int>
   >& value
) {
   if (variable == 1) {
      AxisVelocity axisVelocity = std::get<0>(value);
      Function1(axisVelocity.first, axisVelocity.second);
   } else if (variable == 2) {
      Function2(std::get<1>(value));
   }
}

SetVariable(1, AxisVelocity(axis, velocity));

Таким образом, во время выполнения будет выдано исключение (из std::get), если тип не соответствует аргументу.

Это, конечно, можно было бы усовершенствовать, например, создав тип-обертку вокруг std::variant, который также выполняет неявные преобразования между похожими типами, или используя варадический SetVariable(variable, Args...), который пересылает на SetVariable(variable, std::tuple<Args...>), и т. д.


Другое решение — сделать перечисление variable также определяемым во время компиляции, например:

template<int Variable, class... Args>
void SetVariable(Args&&... args) {
    if constexpr(Variable == 1) {
        Function1(args...);
    } else if constexpr(Variable == 2) {
        Function2(args...);
    }
}

SetVariable<1>(axis, velocity);

if constexpr гарантирует, что неиспользованные ветки (в зависимости от времени компиляции Variable) не компилируются, и поэтому его существование Function2(int, double) не требуется.

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


Другой способ — использовать SFINAE со вспомогательной функцией, например CallIfPossible, которая вызывает функцию, если она может принимать эти аргументы, или, если нет, возвращает false:

#include <vector>
#include <utility>

bool Function1(int axis, double velocity) { return true; }
bool Function2(const std::vector<int>& data) { return true; }

template<class Function, class... Args>
auto CallIfPossible(const Function& fct, Args&&... args) -> decltype(fct(std::forward<Args>(args)...)) {
    // SFINAE: template is only instantiated if fct accepts these arguments
    return fct(std::forward<Args>(args)...);
}
template<class Function>
bool CallIfPossible(const Function&, ...) {
    // fallback
    return false;
    // or maybe throw an exception
}

template<class... Args>
bool SetVariable(int variable, Args&&... args) {
    switch(variable) {
    case 1:
        return CallIfPossible(Function1, std::forward<Args>(args)...);
    case 2:
        return CallIfPossible(Function2, std::forward<Args>(args)...);
    }
    return false;
}

int main() {
    int axis = 1;
    double velocity = 10.0;
    SetVariable(1, axis, velocity);
    
    std::vector<int> data;
    SetVariable(2, data);
}

Это работает начиная с C++11. Внутри SetVariable компилятор по-прежнему будет генерировать экземпляры шаблонов для всех комбинаций. Например, также CallIfPossible(Function2, int, double) за звонок Function2 с помощью int, double.

Но тогда во время замены шаблона decltype(Function2(axis, velocity)) (что обычно приводит к bool) не может быть вычислено, потому что нет перегрузки Function2, которая принимала бы аргументы (int, double). Но это не приводит к ошибке компиляции («сбой замены не является ошибкой»).

Поэтому вместо этого компилятор использует специализацию шаблона CallIfPossible(Function2, ...), которая всегда возвращает false. И он никогда не создает экземпляр функции шаблона, которая вызывала бы Function2(int, double).

Понятно, спасибо за объяснение, но у меня будет много функций в SetVariable, поэтому я не могу выполнить первое решение из-за разных аргументов, второе решение работает, но мне нужно решение на C++ 14, а constexpr равен 17, у нас есть более старая система, которая тоже буду использовать его, так что С++ 14

MBDarkstrike 14.08.2024 12:17

Добавлено еще одно решение, работающее с SFINAE.

tmlen 14.08.2024 15:55

Поскольку вы можете использовать переменную в качестве значения времени компиляции, в C++14 вы можете выполнить некоторую диспетчеризацию тегов вместо способа if-constexpr в C++17.

auto getFunction(std::integral_constant<int, 1>)
{
     return [](auto&&... args){ return Function1(((decltype(args))args)...); };
}
auto getFunction(std::integral_constant<int, 2>)
{
     return [](auto&&... args){ return Function2(((decltype(args))args)...); };
}

А потом

template <int Variable, typename... Args>
auto SetVariable(Args&&... args)
{
    return getFunction(std::integral_constant<int, Variable>{})(std::forward<Args>(args)...);
}

Хотя я бы использовал enum вместо int.

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