Я работал над неким обобщенным c кодом и думал, что он создаст хороший интерфейс для набора классов, который я планирую использовать в варианте, если бы у меня мог быть набор функций с тем же именем и параметрами, но потенциально разные типы возврата. Например, возможно, я хотел бы иметь три класса, которые реализуют функцию, которая может возвращать int
или std::vector<int>
или void
, где возвращаемые значения представляют некоторую последовательность сигналов, которые распространяются в системе наружу.
struct X{
//...
int update(double){
return 5;
}
};
struct Y{
//...
void update(double){
//Do something useful
}
};
struct Z{
//...
std::vector<int> update(double){
return {1,2,3,4};
}
};
Затем, в какой-то более поздний момент, я мог бы иметь обобщенную функцию c (например, выступать в качестве посетителя варианта), например
template<class T>
void updateHandler(T& object){
auto ret = object.update(3.14);
if constexpr(std::is_same_v<decltype(ret), int>){
//Do something with an integer.
}else if constexpr(std::is_same_v<decltype(ret), std::vector<int> >){
//Do something with a vector of integers
}
}
Но это не сработает, если updateHandler
вызывается для объекта типа Y
, поскольку auto
будет выводиться как void
. Хотя можно проверить условие, что возвращаемый тип недействителен раньше, через decltype
, это приведет к чему-то вроде
template<class T>
void updateHandler(T& object){
if constexpr(std::is_void_v<decltype(object.update())>){
object.update();
}else{
auto ret = object.update();
if constexpr(std::is_same_v<decltype(ret), int>){
//Do something with an integer.
}else if constexpr(std::is_same_v<decltype(ret), std::vector<int> >){
//Do something with a vector of integers
}
}
}
, где не совсем ясно, что именно мы делаем, и каким-то образом мы ' Нам удалось трижды (!) продублировать весь наш вызов функции в коде, не имея возможности уменьшить это число каким-либо очевидным способом.
Я также надеялся, что, возможно, могут быть какие-то прикольные правила, чтобы справиться с этим случай, так же, как можно передать void
возвращаемое значение в generi c так же, как
[](auto& object){ return object.update(3.14); }
законно - я пытался, например, использовать шаблон, который может преобразовать пустые значения
template<class T>
decltype(auto) coverVoid(T&& input){
return input;
}
std::monostate coverVoid(void){
return {};
}
, но это не работает, поскольку void
на самом деле не является аргументом (и я бы не ожидал, что это сработает, поскольку void
является неполным типом и Вы не можете взять ссылку на это тоже). Лучшее, что я могу придумать, - это спрятать все повторения в обобщенном c, например:
template<class Call, class Handle>
void handleMaybeVoid(Call&& call, Handle&& handle){
if constexpr(std::is_void_v<decltype(call())>){
call();
//Could then call handle(); if we wanted to handle void
}else{
handle(call());
}
}
, и вызвать его для реализации updateHandler
.
Пока я думаю есть достаточно простое решение этой проблемы (просто верните пустой тип вместо void
в update
- хотя это приводит к довольно бесполезной строке return {};
), меня удивляет, что возврат void
выглядит так трудно обрабатывать в общем коде c. Есть ли лучший способ справиться с возвращаемыми значениями, которые могут быть недействительными?