首页 > 解决方案 > 将 lambda 作为参数的约定

问题描述

我正在编写一个 C++ 应用程序,并在 lambda 参数上抽象了一个方法。我最初使用以下签名:

void atomically(void (*block)(T& value, bool& retry))

但是我发现您不能将任意 lambda 作为函数指针传递—— lambda 必须没有捕获。我可以使用

void atomically(const std::function<void (T&, bool&)>& block)

除了在这部分代码中非常重要的是,我们避免在堆上分配,并创建一个std::function可能的分配。(是否/何时可以保证?)

最后,我可以使用:

template<class F>
void atomically(const F& block) {
    // use block() as if it had been declared as above
}

除了我觉得这是不可接受的,因为它允许block通过值而不是通过引用来获取参数,这将是一个容易犯的错误并且调试起来非常微妙。

将 lambda 作为参数的正确约定是什么?或者,在最终的模板解决方案中,有没有办法确保block通过引用获取其参数?

标签: c++lambda

解决方案


您可能想要一些function_view(取自vittorioromeo)(顾名思义,没有所有权,所以不要存储它):

template <typename TSignature>
class function_view;

template <typename TReturn, typename... TArgs>
class function_view<TReturn(TArgs...)> final
{
private:
    using signature_type = TReturn(void*, TArgs...);

    void* _ptr;
    TReturn (*_erased_fn)(void*, TArgs...);

public:
    template <typename T, typename = std::enable_if_t<
                              std::is_callable<T&(TArgs...)>{} &&
                              !std::is_same<std::decay_t<T>, function_view>{}>>
    function_view(T&& x) noexcept : _ptr{(void*)std::addressof(x)}
    {
        _erased_fn = [](void* ptr, TArgs... xs) -> TReturn {
            return (*reinterpret_cast<std::add_pointer_t<T>>(ptr))(
                std::forward<TArgs>(xs)...);
        };
    }

    decltype(auto) operator()(TArgs... xs) const
        noexcept(noexcept(_erased_fn(_ptr, std::forward<TArgs>(xs)...)))
    {
        return _erased_fn(_ptr, std::forward<TArgs>(xs)...);
    }
};

然后你可以使用:

void atomically(const function_view<void (T&, bool&)>& block)

推荐阅读