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

Amb*_*nan 31 c++

我有C++代码包装任意lambda并返回lambda的结果.

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

这有效,除非Freturn void(error: variable has incomplete type 'void').我想用a ScopeGuard来运行bar,但我不想barfn投掷时运行.有任何想法吗?

Nir*_*man 22

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

template <class T>
struct CallAndStore {
    template <class F>
    CallAndStore(F f) : t(f()) {}
    T t;
    T get() { return std::forward<T>(t); }
};
Run Code Online (Sandbox Code Playgroud)

并专注:

template <>
struct CallAndStore<void> {
    template <class F>
    CallAndStore(F f) { f(); }
    void get() {}
};
Run Code Online (Sandbox Code Playgroud)

您可以通过小型工厂功能提高可用性:

template <typename F>
auto makeCallAndStore(F&& f) -> CallAndStore<decltype(std::declval<F>()())> {
    return {std::forward<F>(f)};
}
Run Code Online (Sandbox Code Playgroud)

然后使用它.

template <typename F>
auto wrapAndRun(F fn) {
    // foo();
    auto&& result = makeCallAndStore(std::move(fn));
    // bar();
    return result.get();
}
Run Code Online (Sandbox Code Playgroud)

编辑:内置std::forward强制转换get,这似乎也可以处理正确返回函数的引用.


Már*_*ldi 13

新的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所说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;
}
Run Code Online (Sandbox Code Playgroud)


Mas*_*nes 5

另一个技巧可能是利用逗号运算符,如:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • @MassimilianoJanes重载逗号运算符是C++社区(自包含)的一大块将告诉你永远不会做的事情之一.是的,我理解了这个想法,但这是100%,死了,那种黑魔法超载,所以很多人和风格指南都建议反对.如果真的,真的没有其他方式可能值得考虑,但已经有4种解决方案,这些都是可取的. (8认同)
  • @NirFriedman恭敬地,我不同意; 首先,逗号运算符*用于现有广泛使用的库(boost,Eigen引用我想到的第一个),所以声称*每个人*说*从不*使用它,简直是假的.其次,人们应该(理所当然地)避免超载逗号运算符的原因不适用于此,您可以轻松地使其安全. (5认同)
  • 第三,我们不知道OP代码的最终目标和范围; 他可能只需要一个问题的技术解决方案,埋在实施细节中.这个答案是OP问题的有效解决方案.Afaik,仅仅因为它"感觉"不好而贬低它就违背了投票的意义. (5认同)
  • 除此之外,它还没有正确处理返回引用类型的函数(容易修复),并且它也使RVO无效,即当我运行此代码时,有2个无关的析构函数调用,而不是大多数中的一个或零.其他解决方案. (2认同)
  • @NirFriedman嗯,您在评论中提到的答案提到逗号可以用作元编程技术(有时确实用于其他合法用途).坦率地说,我认为指导方针存在(或者应该存在)作为一种手段来传达*原因*为什么事情是坏的,而不是出售关于一个应该永远/永远不会做的教条.关于downvote问题,我认为投票机制已经允许建立答案层次; 如果我正确阅读了SO帮助和meta帖子,那么downvotes应仅保留给非有用或错误的答案. (2认同)

Kil*_*ian 5

您可以查看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)();
}
Run Code Online (Sandbox Code Playgroud)

因此,在没有错误的情况下,执行顺序是:1.foo(),2. f(),3.bar().如果出现异常,订单为:1.foo(),2.f()