Подтвердить что ты не робот

Обработка назначения void в общем программировании на С++

У меня есть код С++, который обертывает произвольную лямбда и возвращает результат лямбда.

template <typename F>
auto wrapAndRun(F fn) -> decltype(F()) {
    // foo();
    auto result = fn();
    // bar();
    return result;
}

Это работает, если F не возвращает void (error: variable has incomplete type 'void'). Я думал использовать ScopeGuard для запуска bar, но я не хочу, чтобы bar выполнялся, если fn throws. Любые идеи?

4b9b3361

Ответ 1

Другим трюком может быть использование оператора запятой, например:

struct or_void {};

template<typename T>
T&& operator,( T&& x, or_void ){ return std::forward<T>(x); }

template <typename F>
auto wrapAndRun(F fn) -> decltype(fn()) {
    // foo();
    auto result = ( fn(), or_void() );
    // bar();
    return decltype(fn())(result);
}

Ответ 2

Вы можете написать простой класс-оболочку, который обрабатывает эту его часть:

template <class T>
struct CallAndStore {
    template <class F>
    CallAndStore(F f) : t(f()) {}
    T t;
    T get() { return std::forward<T>(t); }
};

И специализируйтесь:

template <>
struct CallAndStore<void> {
    template <class F>
    CallAndStore(F f) { f(); }
    void get() {}
};

Вы можете улучшить удобство использования с помощью небольшой функции factory:

template <typename F>
auto makeCallAndStore(F&& f) -> CallAndStore<decltype(std::declval<F>()())> {
    return {std::forward<F>(f)};
}

Затем используйте его.

template <typename F>
auto wrapAndRun(F fn) {
    // foo();
    auto&& result = makeCallAndStore(std::move(fn));
    // bar();
    return result.get();
}

Изменить: с std::forward лить внутри get, это также похоже на правильное возвращение ссылки из функции.

Ответ 3

Новое дополнение С++ 17 if constexpr может быть полезно здесь. Вы можете выбрать, возвращать ли результат fn() во время компиляции:

#include <type_traits>

template <typename F>
auto wrapAndRun(F fn) -> decltype(fn())
{
    if constexpr (std::is_same_v<decltype(fn()), void>)
    {
        foo();
        fn();
        bar();
    }
    else
    {
        foo();
        auto result = fn();
        bar();
        return result;
    }
}

Как вы сказали, С++ 2a также является опцией, вы также можете использовать понятия, помещая ограничение на функцию:

template <typename F>
  requires requires (F fn) { { fn() } -> void }
void wrapAndRun(F fn)
{
    foo();
    fn();
    bar();
}

template <typename F>
decltype(auto) wrapAndRun(F fn)
{
    foo();
    auto result = fn();
    bar();
    return result;
}

Ответ 4

Вы можете взглянуть на ScopeGuard от Alexandrescu: ScopeGuard.h Он выполняет код только тогда, когда не было никаких исключений.

template<class Fn>
decltype(auto) wrapAndRun(Fn&& f) {
  foo();
  SCOPE_SUCCESS{ bar(); }; //Only executed at scope exit when there are no exceptions.
  return std::forward<Fn>(f)();
}

Таким образом, в случае отсутствия ошибок порядок выполнения: 1. foo(), 2. f(), 3. bar(). И в случае исключения порядок: 1. foo(), 2. f()

Ответ 5

Решение с помощью SFINAE, идея состоит в том, чтобы сделать внутреннюю функцию void фактически возвратой int - надеюсь, компилятор оптимизирует это. На внешней стороне wrapAndRun будет возвращен тот же тип, что и завернутая функция.

http://coliru.stacked-crooked.com/a/e84ff8f74b3b6051

#include <iostream>

template <typename F>
auto wrapAndRun1(F fn) -> std::enable_if_t<!std::is_same_v<std::result_of_t<F()>, void>, std::result_of_t<F()>> {
    return fn();
}


template <typename F>
auto wrapAndRun1(F fn) -> std::enable_if_t<std::is_same_v<std::result_of_t<F()>, void>, int> {
    fn();
    return 0;
}

template <typename F>
auto wrapAndRun(F fn) -> std::result_of_t<F()> {
    // foo();
    [[maybe_unused]] auto result = wrapAndRun1(fn);    
    // bar();        
    if constexpr (std::is_void_v<std::result_of_t<F()>>)
        return;
    else
        return result;
}

int main()
{
    wrapAndRun([] { std::cout << "with return \n"; return 0; });
    wrapAndRun([] { std::cout << "no return \n"; });
}

Ответ 6

Это довольно неудобное и не расширяемое решение, но очень короткое и простое, и оно поддерживает RVO, что может быть значительным для некоторых типов возврата:

template <typename F>
auto wrapAndRun(F fn) -> decltype(F()) {
    // foo();
    char c;
    auto runner = std::unique_ptr<char, decltype(bar)>( &c, bar );
    try {
        return fn();
    }
    catch( ... ) {
        runner.release();
        throw;
    }
}