函数参数返回void或non-void类型

bar*_*top 19 c++ templates c++14

我正在为将来的库编写一些通用代码。我在模板函数中遇到以下问题。考虑下面的代码:

template<class F>
auto foo(F &&f) {
    auto result = std::forward<F>(f)(/*some args*/);
    //do some generic stuff
    return result;
}
Run Code Online (Sandbox Code Playgroud)

除非我将返回的函数传递给它,否则它将正常工作void

foo([](){});
Run Code Online (Sandbox Code Playgroud)

现在,当然,我现在可以使用一些std::enable_if魔术来检查返回类型,并对返回的函数执行专门化的操作void,如下所示:

template<class F, class = /*enable if stuff*/>
void foo(F &&f) {
    std::forward<F>(f)(/*some args*/);
    //do some generic stuff
}
Run Code Online (Sandbox Code Playgroud)

但这将完全复制实际上逻辑上等效的功能的代码。能否以一种优雅的方式,以通用的方式轻松地对- void返回和非void返回功能同时进行?

编辑:f()我想做的功能和通用的东西之间有数据依赖关系,所以我不考虑这样的代码:

template<class F>
auto foo(F &&f) {
    //do some generic stuff
    return std::forward<F>(f)(/*some args*/);
}
Run Code Online (Sandbox Code Playgroud)

max*_*x66 15

如果您可以将“一些通用的东西”放在bar类的析构函数中(在安全性try / catch块中,如果不确定如Drax所指出的那样不会引发异常),则只需编写

template <typename F>
auto foo (F &&f)
 {
   bar b;

   return std::forward<F>(f)(/*some args*/);
 }
Run Code Online (Sandbox Code Playgroud)

因此,编译器compute f(/*some args*/),exec的析构函数b并返回计算值(或什么都不返回)。

可以看到return func();func()函数返回的位置void是完全合法的。

  • 额外的提示:“一般的东西”最好不要扔东西 (2认同)

Sam*_*hik 9

某些地方的一些专业化是必要的。但是这里的目标是避免专门化函数本身。但是,您可以专门研究一个助手类。

已在gcc 9.1和上进行了测试-std=c++17

#include <type_traits>
#include <iostream>

template<typename T>
struct return_value {


    T val;

    template<typename F, typename ...Args>
    return_value(F &&f, Args && ...args)
        : val{f(std::forward<Args>(args)...)}
    {
    }

    T value() const
    {
        return val;
    }
};

template<>
struct return_value<void> {

    template<typename F, typename ...Args>
    return_value(F &&f, Args && ...args)
    {
        f(std::forward<Args>(args)...);
    }

    void value() const
    {
    }
};

template<class F>
auto foo(F &&f)
{
    return_value<decltype(std::declval<F &&>()(2, 4))> r{f, 2, 4};

    // Something

    return r.value();
}

int main()
{
    foo( [](int a, int b) { return; });

    std::cout << foo( [](int a, int b) { return a+b; }) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)


Bar*_*rry 5

我认为,执行此操作的最佳方法是实际更改调用可能无效的函数的方式。基本上,我们更改void返回的类,而返回某种类类型Void,出于所有意图和目的,该类类型是同一件事,并且没有用户真正关心。

struct Void { };
Run Code Online (Sandbox Code Playgroud)

我们需要做的就是包装调用。以下使用C ++ 17名称(std::invokestd::invoke_result_t),但是它们都可以在C ++ 14中实现,而不必大惊小怪:

// normal case: R isn't void
template <typename F, typename... Args, 
    typename R = std::invoke_result_t<F, Args...>,
    std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}

// special case: R is void
template <typename F, typename... Args, 
    typename R = std::invoke_result_t<F, Args...>,
    std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args) {
    // just call it, since it doesn't return anything
    std::invoke(std::forward<F>(f), std::forward<Args>(args)...);

    // and return Void
    return Void{};
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,您可以直接以要编写的方式编写要编写的代码:

template<class F>
auto foo(F &&f) {
    auto result = invoke_void(std::forward<F>(f), /*some args*/);
    //do some generic stuff
    return result;
}
Run Code Online (Sandbox Code Playgroud)

而且,您不必将所有逻辑推到析构函数中,也不必通过专门化来复制所有逻辑。以foo([]{})返回Void而不是的代价为代价void,这并不是很多费用。

然后如果经常虚空中不断采用,所有你需要做的就是换出invoke_voidstd::invoke