将SFINAE与通用lambda一起使用

时间:2015-07-06 19:17:18

标签: c++ lambda c++14 sfinae generic-lambda

通用lambda可以利用“替换失败不是错误”规则吗?示例

auto gL = 
    [](auto&& func, auto&& param1, auto&&... params) 
        -> enable_if_t< is_integral<
            std::decay_t<decltype(param1)>
        >::value>
    {
        // ...
    };

auto gL =  
     [](auto&& func, auto&& param1, auto&&... params) 
        -> enable_if_t< !is_integral<
            std::decay_t<decltype(param1)>
        >::value>
    {
        // ...
    };

是否有任何变通方法或计划将其包含在语言中?此外,由于通用lambda是引擎盖下的模板化功能对象,这是不是有点奇怪,这是不能做到的?

3 个答案:

答案 0 :(得分:7)

Lambdas是引擎盖下的功能对象。通用lambda是具有模板operator() s。

的函数对象
template<class...Fs>
struct funcs_t{};

template<class F0, class...Fs>
struct funcs_t<F0, Fs...>: F0, funcs_t<Fs...> {
  funcs_t(F0 f0, Fs... fs):
    F0(std::move(f0)),
    funcs_t<Fs...>(std::move(fs)...)
  {}
  using F0::operator();
  using funcs_t<Fs...>::operator();
};
template<class F>
struct funcs_t<F>:F {
  funcs_t(F f):F(std::move(f)){};
  using F::operator();
};
template<class...Fs>
funcs_t< std::decay_t<Fs>... > funcs(Fs&&...fs) {
  return {std::forward<Fs>(fs)...};
}

auto f_all = funcs( f1, f2 )生成的对象是f1f2的重载。

auto g_integral = 
  [](auto&& func, auto&& param1, auto&&... params) 
    -> std::enable_if_t< std::is_integral<
        std::decay_t<decltype(param1)>
    >{}>
  {
    // ...
  };

auto g_not_integral =  
 [](auto&& func, auto&& param1, auto&&... params) 
    -> std::enable_if_t< !std::is_integral<
        std::decay_t<decltype(param1)>
    >{}>
{
    // ...
};

auto gL = funcs( g_not_integral, g_integral );

并且调用gL将对两个lambdas进行SFINAE友好重载解析。

funcs_t的线性继承中,上面做了一些可以避免的虚假动作。在工业质量库中,我可能会创建继承二进制而不是线性(以限制模板的实例化深度和继承树的深度)。

顺便说一句,我知道SFINAE启用lambdas有4个理由。

首先,使用新的std::function,您可以在多个不同的回调签名上重载函数。

其次,上面的技巧。

第三,讨论一个函数对象,它在它具有正确数量和类型的args时进行评估。

Forth,自动元组解包和类似。如果我使用延续传递样式,我可以询问传入的延续,如果它将接受解压缩的元组,或未来的非捆绑等等。

答案 1 :(得分:2)

SFINAE的使用是在解析给定的函数或模板时从候选集中移除重载或特化。在你的情况下,我们有一个lambda - 这是一个带有operator()的函子。没有过载,因此没有理由使用SFINAE 1 。 lambda是通用的,它使operator()成为函数模板,这一事实并没有改变这一事实。

但是,您实际上不需要区分不同的返回类型。如果func为给定参数返回void,您仍然可以return。你只是不能将它分配给临时。但你不必这样做:

auto time_func = [](auto&& func, auto&&... params) {
    RaiiTimer t;
    return std::forward<decltype(func)>(func)(
        std::forward<decltype(params)>(params)...); 
};

只需编写一个RaiiTimer,其构造函数启动一个计时器,其析构函数将其停止并打印结果。无论func的返回类型如何,这都可以。

如果你需要比这更复杂的东西,那么这就是你应该prefer a functor over a lambda的情况之一。

1 实际上,正如Yakk指出的那样,SFINAE仍然可以非常方便地检查你的功能是否是可调用时段,这不是你想要解决的问题 - 所以在这种情况下,仍然不是很有帮助。

答案 2 :(得分:2)

一般的lambda只能有一个体,所以SFINAE在这里没什么用处。

一种解决方案是将调用打包到一个可以存储结果的类中,并专门用于LocationCode返回类型,将def spatLoc(ImageName): if ImageName <=70: LocationCode = 1 elif ImageName >70 and ImageName <=90: LocationCode = 2 return LocationCode df['test'] = df.apply(spatLoc(df['ImageName']) 特殊处理封装在远离lambda的位置。只需很少的开销,您就可以使用线程库工具执行此操作:

void

如果您想避免voidauto gL = [](auto&& func, auto&&... params) { // start a timer using Ret = decltype(std::forward<decltype(func)>(func)( std::forward<decltype(params)>(params)...)); std::packaged_task<Ret()> task{[&]{ return std::forward<decltype(func)>(func)( std::forward<decltype(params)>(params)...); }}; auto fut = task.get_future(); task(); // stop timer and print elapsed time return fut.get(); }; 的开销,可以轻松编写自己的版本:

packaged_task