C++泛型编程中空赋值的处理

7bsow1i6  于 2023-02-10  发布在  其他
关注(0)|答案(7)|浏览(140)

我有一个C++代码,它可以 Package 一个任意的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,但我不希望在fn抛出时运行bar。有什么想法吗?
P.S.我后来发现有a proposal to fix this inconsistency

55ooxyrt

55ooxyrt1#

您可以编写一个简单的 Package 器类来处理这一部分:

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强制转换,这似乎也可以正确处理从函数返回引用。

nnsrf1az

nnsrf1az2#

新的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++20也是一个选项,你也可以利用概念,对函数施加约束:

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

t5fffqht3#

您可以查看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()

juzqafwq

juzqafwq4#

另一个技巧可能是利用逗号操作符,类似于:

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

bejyjqdl5#

SFINAE的解决方案,其思想是在内部使void函数实际返回int -希望编译器将优化这一点。在外部wrapAndRun将返回与 Package 函数相同的类型。
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"; });
}
643ylb08

643ylb086#

解决这个问题最方便的方法是将void Package 成一个你可以实际使用的类型,沿着一个将void转换成这个常规类型的std::invoke版本。

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,它只是一个空类型。
这很方便,因为您将所有特殊情况的空处理放在一个位置(在本例中是库),然后剩下的代码看起来就正常了。你不必为每一种用法编写任何定制的特殊情况,你不必根据你选择的解决方案重新组织你的代码逻辑-您仍然可以在fn之前和之后调用foo()bar()

plicqrtu

plicqrtu7#

这是一个非常笨拙的、不可扩展的解决方案,但是非常简短,并且它支持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;
    }
}

相关问题