c++ 返回void或非void类型的函数参数

plicqrtu  于 2023-05-08  发布在  其他
关注(0)|答案(5)|浏览(255)

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

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

它将正常工作,除非我向它传递一个返回void的函数,如:

foo([](){});

现在,当然,我可以使用一些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
}

但这会严重地重复实际上逻辑上等效的函数的代码。对于void返回函数和非void返回函数,能否以一种通用的方式轻松地以优雅的方式完成这一点?
编辑:函数f()和我想做的通用东西之间存在数据依赖,所以我不考虑这样的代码:

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

t3psigkw1#

如果你可以把“一些通用的东西”放在bar类的析构函数中(在一个安全的try/catch块中,如果你不确定不会抛出异常,就像Drax指出的那样),你可以简单地写:

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

   return std::forward<F>(f)(/*some args*/);
 }

因此编译器计算f(/*some args*/),执行b的析构函数并返回计算值(或不返回)。
注意return func();,其中func()是返回void的函数,是完全法律的的。

4zcjmb1e

4zcjmb1e2#

在某个地方,某种专业化是必要的。但这里的目标是避免专门化函数本身。但是,您可以专门化一个帮助器类。
使用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;
}
b1payxdu

b1payxdu3#

在我看来,做到这一点的最好方法是真正改变调用可能返回void的函数的方式。基本上,我们改变了返回void的类类型,而不是返回一些类类型Void,也就是说,出于所有意图和目的,都是一样的,没有用户真的会在意。

struct Void { };

我们需要做的就是 Package 调用。下面的代码使用了C17的名字(std::invokestd::invoke_result_t),但它们都可以在C14中实现,没有太多的麻烦:

// 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{};
}

这样做的好处是,你可以直接写你想写的代码开始,在你想写的方式:

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

你不必把所有的逻辑都放在析构函数中,也不必通过特殊化来复制所有的逻辑。代价是foo([]{})返回Void,而不是void,这并不是很大的代价。
然后,如果Regular Void被采用,您所要做的就是将invoke_void替换为std::invoke

6ovsh4lw

6ovsh4lw4#

在C++中创建一个void函数(或非返回值函数),它将接受两个整数值作为参数,并将显示这两个数字之间的数字(包括两个数字)

fnx2tebb

fnx2tebb5#

如果你需要在“一些通用的东西”中使用result(在非void的情况下),我提出了一个基于if constexpr的解决方案(不幸的是,在C++17之前不是)。
老实说,不太优雅。
首先,检测f的“true返回类型”(给定参数)

using TR_t = std::invoke_result_t<F, As...>;

接下来是一个constexpr变量,以查看返回的类型是否为void(只是为了简化以下代码)

constexpr bool isVoidTR { std::is_same_v<TR_t, void> };

现在我们定义一个(潜在的)“假返回类型”:int当真实返回类型为void时,则为TR_t

using FR_t = std::conditional_t<isVoidTR, int, TR_t>;

然后,我们将指向结果值的智能指针定义为指向“假返回类型”的指针(在void情况下为int

std::unique_ptr<FR_t>  pResult;

通过一个指针,而不是一个简单的“假返回类型”变量,我们也可以在TR_t不是默认可构造或不可赋值时进行操作(限制,由巴里指出(谢谢),这个答案的第一个版本)。
现在,使用if constexpr,执行f的两种情况(恕我直言,这是最糟糕的部分,因为我们必须编写两次相同的f调用)

if constexpr ( isVoidTR )
   std::forward<F>(f)(std::forward<As>(args)...);
else
   pResult.reset(new TR_t{std::forward<F>(f)(std::forward<As>(args)...)});

在此之后,“一些通用的东西”可以使用result(在非void情况下)和isVoidTR)。 下一篇:if constexpr`

if constexpr ( isVoidTR )
   return;
else
   return *pResult;

正如巴里所指出的,这个解决方案有一些重要的缺点,因为(不是void情况)

  • 要求拨款
  • 需要一个额外的副本对应的return
  • 如果TR_tf()返回的类型)是引用类型,则根本不起作用

无论如何,下面是一个完整的C++17编译示例

#include <memory>
#include <type_traits>

template <typename F, typename ... As>
auto foo (F && f, As && ... args)
 {
   // true return type
   using TR_t = std::invoke_result_t<F, As...>;

   constexpr bool isVoidTR { std::is_same_v<TR_t, void> };

   // (possibly) fake return type
   using FR_t = std::conditional_t<isVoidTR, int, TR_t>;

   std::unique_ptr<FR_t>  pResult;

   if constexpr ( isVoidTR )
      std::forward<F>(f)(std::forward<As>(args)...);
   else
      pResult.reset(new TR_t{std::forward<F>(f)(std::forward<As>(args)...)});

   // some generic stuff (potentially depending from result,
   // in non-void cases)

   if constexpr ( isVoidTR )
      return;
   else
      return *pResult;
 }

int main ()
 {
   foo([](){});

   //auto a { foo([](){}) };  // compilation error: foo() is void

   auto b { foo([](auto a0, auto...){ return a0; }, 1, 2l, 3ll) };

   static_assert( std::is_same_v<decltype(b), int> );

 }

相关问题