Я хочу иметь 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 аргумента и я не вызываю это событие, если у меня более одного случая, ошибка ампера
returnValue = Function2(std::forward<Args>(args)...);
должен быть действительным, даже если ветку не берут.
Сравните это с обычными if
и else
, где оба кода ветки должны быть действительными.
«Я триаду с ChatGPT...» ИИ не является хорошим/надежным источником проблем с кодированием. книга намного лучше.
но проблема в том, что я получил неправильный номер аргумента.
Проблема в том, что код внутри любой ветки, показанной на рисунке 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
Добавлено еще одно решение, работающее с SFINAE.
Поскольку вы можете использовать переменную в качестве значения времени компиляции, в 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
.
пожалуйста, опубликуйте минимально воспроизводимый пример.