43

我正在寻找的代码如下所示。

bool Func1(int Arg1, C++11LambdaFunc Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }
}

稍后我将使用此代码。

Func1(12, [](int D) -> bool { ... } );
4

3 回答 3

60

基本版本,用于头文件:

template<typename Lambda>
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

更复杂的版本,如果你想从你的实现中分离你的接口(它有运行时间成本):

bool Func1(int Arg1, std::function<bool(int)> Arg2){
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

std::functionpImpl使用类型擦除围绕您的 lambda 创建一个自定义创建的包装器,然后公开一个使用该模式将其转发到自定义创建的包装器的非虚拟接口。1

或者,用不太专业的术语来说,std::function<bool(int)>是一个类,它可以包装几乎任何你可以像函数一样调用的东西,传递一个与传递 a 兼容的参数,int它返回与返回 a 兼容的东西bool

通过 a 调用std::function的运行时间成本大致等于virtual函数调用(由上述类型擦除引起),并且当您创建它时,它必须复制传入的函数对象(又名函子)的状态(这可能很便宜-- 无状态 lambdas,或通过引用捕获参数的 lambdas -- 或在某些其他情况下昂贵)并将其存储(通常在免费存储或堆上,这是有成本的),而纯模板版本可以“内联”在调用点(即,不仅成本低于函数调用,编译器甚至可以优化函数调用和返回边界!)

如果您想在没有所有运行时成本的情况下拆分接口/实现std::function,您可以滚动自己的 function_ref (在中,因为这减少了一些样板文件):

template<class Sig>
struct function_ref;

template<class R, class...Args>
struct function_ref<R(Args...)> {
  R operator()(Args...args) const {
    return pf(state, std::forward<Args>(args)...);
  }
  function_ref()=default;
  function_ref(function_ref const&)=default;
  function_ref& operator=(function_ref const&)=default;
  explicit operator bool()const{ return pf!=nullptr; }

  // this overload reduces indirection by 1 step
  // and allows function_ref<Sig> to resolve overloads
  // on an overload set sometimes.
  function_ref( R(*f)(Args...) ):
    pf([](State const& state, Args&&...args)->R{
      return reinterpret_cast<R(*)(Args...)>(state.pfunstate)(std::forward<Args>(args)...);
    })
  {
    state.pfunstate = reinterpret_cast<void(*)()>(f);
  }

  // this grabs anything callable (that isn't this own type)
  // and stores a pointer to it to call later.
  template<class F>
  requires (
    std::is_convertible_v<
      std::invoke_result_t< std::remove_reference_t<F>, Args... >, R
    >
    && !std::is_same_v< std::decay_t<F>, function_ref >
  )
  function_ref( F&& f ):
    pf([](State const& state, Args&&...args)->R{
      return (*(std::remove_reference_t<F>*)state.pstate)(std::forward<Args>(args)...);
    })
  {
    state.pstate = std::addressof(f);
  }
private:
  union State {
    void* pstate = nullptr;
    void(*pfunstate)();
  };
  State state;
  R(*pf)(State const&, Args&&...) = nullptr;
};
// a deduction guide permitting function_ref{foo} to work
// if foo is a non-overloaded function name.
template<class R, class...Args>
function_ref( R(*)(Args...) )->function_ref<R(Args...)>;

活生生的例子

std::function这消除了通过从中删除所有权语义并仅进行类型擦除调用来进行任何分配的需要。

第一个示例的精美版本,它还可以更好地处理一些极端情况:(也必须在头文件中实现,或者在使用的同一翻译单元中实现)

template<typename Lambda>
bool Func1(int Arg1, Lambda&& Arg2){
  if(Arg1 > 0){
    return std::forward<Lambda>(Arg2)(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

它使用一种称为“完美转发”的技术。对于某些函子,这会产生与#1 稍有不同的行为(通常是更正确的行为)。

大多数改进来自&&参数列表中的使用:这意味着传入函子的引用(而不是副本),节省一些成本,并允许传入一个const或非const函子。

如果有人使用了一个相对较新的 C++ 特性,该特性允许方法(包括)覆盖指针的右值/左值状态,这种std::forward<Lambda>(...)变化只会导致行为发生变化。从理论上讲,这可能很有用,但是我看到的基于右值状态实际覆盖的函子数量是is 。当我正在编写严肃的库代码(tm)时,我会为此烦恼,但很少会这样做。operator()thisthis0

还有一件可能的事情需要考虑。假设您想要获取返回bool的函数或返回的函数void,并且如果函数返回void,您希望将其视为返回true。例如,您正在使用一个在迭代某个集合时被调用的函数,并且您希望有选择地支持提前停止。该函数false在它想提前停止时返回,true或者void以其他方式返回。

或者,在更一般的情况下,如果您对一个函数有多个覆盖,其中一个采用一个函数,而另一些在同一位置采用其他类型。

这是可能的,这就是我将要介绍的内容(使用智能适配器或通过 SFINAE 技术)。但是,您最好只创建两个不同的命名函数,因为所需的技术太重了。


1从技术上讲std::function,可以使用魔法仙尘来做它所做的事情,因为它的行为是由标准描述的,而不是它的实现。我正在描述一个简单的实现,它近似于std::function我与之交互的实现的行为。

于 2013-04-19T18:32:06.337 回答
24

第一个解决方案:

您可以使您的Func1()功能成为功能模板

template<typename T>
bool Func1(int Arg1, T&& Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false; // <== DO NOT FORGET A return STATEMENT IN A VALUE-RETURNING
                  //     FUNCTION, OR YOU WILL GET UNDEFINED BEHAVIOR IF FLOWING
                  //     OFF THE END OF THE FUNCTION WITHOUT RETURNING ANYTHING
}

然后,您可以根据需要调用它:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}

第二种解决方案:

如果您不想使用模板,另一种方法(会带来一些运行时开销)是使用std::function

#include <functional>

bool Func1(int Arg1, std::function<bool(int)> Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false;
}

再一次,这将允许您以您想要Func1()的方式调用:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}
于 2013-04-19T18:32:02.200 回答
13

对于那些口味更传统的人,请注意非捕获 lambda 可以转换为函数指针。因此,您可以将上面的函数编写为:

bool Func1(int Arg1, bool (*Arg2)(int)) { ... }

它适用于传统函数lambdas。

于 2013-04-19T18:36:29.797 回答