Хороший способ справиться с возможными возвращаемыми значениями void в обобщенной функции c? - PullRequest
2 голосов
/ 11 февраля 2020

Я работал над неким обобщенным 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. Есть ли лучший способ справиться с возвращаемыми значениями, которые могут быть недействительными?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...