簡體   English   中英

在 C++ 泛型編程中處理 void 賦值

[英]Handling void assignment in C++ generic programming

我有 C++ 代碼包裝任意 lambda 並返回 lambda 的結果。

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

這有效,除非F返回voiderror: variable has incomplete type 'void' )。 我想過使用ScopeGuard來運行bar ,但我不希望barfn拋出時運行。 有任何想法嗎?

PS 我后來發現有一個解決這個不一致的建議

您可以編寫一個簡單的包裝類來處理這部分內容:

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() {}
};

您可以使用小型工廠功能提高可用性:

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();
}

編輯:在get使用std::forward ,這似乎也可以正確處理從函數返回的引用。

新的 C++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;
    }
}

正如你所說的 C++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;
}

另一個技巧可能是利用逗號運算符,例如:

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

你可以看看 Alexandrescu 的 ScopeGuard: 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()

使用 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"; });
}

解決這個問題的最方便的方法是將void包裝在您可以實際使用的類型中,以及將void轉換為該常規類型的std::invoke版本。 使用我的 void 庫(C++17):

template <typename F>
auto wrapAndRun(F fn) -> vd::wrap_void<std::invoke_result_t<F&>>
    // foo();
    auto result = vd::invoke(fn);
    // bar();
    return result;
}

如果fn()不是void ,這將執行與之前相同的操作。 返回任何類型。 如果fn()void ,則result的類型為vd::Void ,它只是一些空類型。

這很方便,因為您將所有特殊情況的 void 處理放在一個位置(在本例中為庫),然后代碼的 rest 看起來……正常。 您不必為每次使用編寫任何定制的特殊情況,也不必根據您選擇的解決方案重新組織代碼的邏輯 - 您仍然可以在fn之前和之后調用foo()bar()正好。

這是非常笨拙且不可擴展的解決方案,但非常簡短且支持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;
    }
}

暫無
暫無

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

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