簡體   English   中英

函數參數返回void或非void類型

[英]Function argument returning void or non-void type

我正在為未來的庫編寫一些通用代碼。 我在模板函數中遇到了以下問題。 請考慮以下代碼:

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*/);
}

如果你可以在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的函數,是完全合法的。

某些專業化是必要的。 但這里的目標是避免專門化功能本身。 但是,您可以專門化一個輔助類。

用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;
}

在我看來,實現這一目標的最佳方法是實際改變你調用可能無效返回函數的方式。 基本上,我們改變返回void的那些代替返回一些類類型Void ,對於所有意圖和目的,同樣的事情並沒有用戶真正關心。

struct Void { };

我們需要做的就是包裝調用。 下面使用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{};
}

這樣做的好處是,您可以按照您想要的方式直接編寫您想要編寫的代碼:

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_voidstd::invoke

如果你需要在“一些通用的東西”中使用result (在非空的情況下),我提出了一個基於if constexpr的解決方案(不幸的是,不是在C ++ 17之前)。

說實話,不是很優雅。

首先,檢測f的“真實返回類型”(給定參數)

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

接下來是一個constexpr變量,看看返回的類型是否為void (只是為了簡化以下代碼)

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

現在,我們定義了一個(可能)“假返回類型”: int當真正的返回類型是voidTR_t否則

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

然后我們將一個指向結果值的智能指針定義為指向“假返回類型”的指針(所以在void case中為int

std::unique_ptr<FR_t>  pResult;

通過指針,而不是類型為“假返回類型”的簡單變量,我們也可以在TR_t不是默認可構造或不可分配時操作(限制,由Barry指出(謝謝),這個答案的第一個版本) 。

現在,使用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 (在非空的情況下)和`isVoidTR)。

總而言之,另一個if constexpr

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

正如Barry所指出的,這個解決方案有一些重要的缺點,因為(不是void情況)

  • 需要分配
  • 需要額外的副本以對應return
  • 如果TR_t (由f()返回的類型)是引用類型,則TR_t

無論如何,以下是完整的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> );

 }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM