当前位置: 首页 > 知识库问答 >
问题:

将SFINAE与通用lambdas一起使用

巫马阳飙
2023-03-14

泛型lambdas能利用“替换失败不是错误”规则吗?例子

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个答案

蔺霄
2023-03-14

SFINAE的用途是在html" target="_blank">解析给定的函数或模板时,从候选集中删除重载或专门化。在您的例子中,我们有一个lambda -它是一个带有单个< code >操作符()的函子。没有重载,所以没有理由使用SFINAE1。lambda是泛型的,这使得它的< code>operator()成为一个函数模板,但这并没有改变这个事实。

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

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

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

如果你需要比这更复杂的东西,那么这就是你应该更喜欢函子而不是lambda的情况之一。

1 个实际上,正如Yak所指出的,SFINAE仍然可以非常方便地检查你的函数是否是可调用的周期,这不是你试图解决的问题 - 所以在这种情况下,仍然不是很有帮助。

卓麒
2023-03-14

一个泛型lambda只能有一个体,所以SFINAE在这里没有多大用处。

一种解决方案是将调用打包到一个类中,该类可以存储结果并专门处理< code>void返回类型,将< code>void特殊处理封装在lambda之外。只需很少的开销,您就可以使用线程库工具来完成这项工作:

auto 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(); 
    };

如果您想避免<code>packaged_task</code>和<code>future</code>的开销,可以很容易地编写自己的版本:

template<class T>
struct Result
{
    template<class F, class... A> Result(F&& f, A&&... args)
        : t{std::forward<F>(f)(std::forward<A>(args)...)} {}
    T t;
    T&& get() { return std::move(t); }
};
template<>
struct Result<void>
{
    template<class F, class... A> Result(F&& f, A&&... args)
        { std::forward<F>(f)(std::forward<A>(args)...); }
    void get() {}
};

auto gL = 
    [](auto&& func, auto&&... params)
    {
        // start a timer
        using Ret = decltype(std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...));
        Result<Ret> k{std::forward<decltype(func)>(func),
            std::forward<decltype(params)>(params)...};
        // stop timer and print elapsed time
        return k.get(); 
    };
陈翰林
2023-03-14

lambda是引擎盖下的功能对象。泛型lambda是带有模板操作符()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)...};
}

< code>auto f_all = funcs( f1,f2 )生成一个对象,该对象是< code>f1和< code>f2的重载。

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 将在两个 lambda 上执行 SFINAE 友好重载解析。

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

顺便说一句,据我所知,SFINAE启用lambdas有4个原因。

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

二、以上招数。

第三,在函数对象具有正确数量和类型的 args 时进行计算,从而查询函数对象。

第四,自动元组解包等。如果我使用延续传递方式,我可以询问传入的延续是否接受解包的元组,或者将来解包的元组,等等。

 类似资料:
  • 问题内容: 因此,我一直在为这个(应该是)简单的练习而绞尽脑汁,以使该程序将日期字符串转换为对象,对其进行格式化,并在完成后将其作为字符串再次返回。 这是程序的最后一点,它从文件中获取一小段文本,将其分解为单独的记录,然后将记录分解为单独的数据并将它们分配给个人对象。 我已经在多个位置检查了该代码,并且该代码完全执行了应该执行的操作,直到调用了format函数(该函数抛出)为止。为对象分配了应该分

  • 问题内容: 我想在目录中获取具有特定扩展名的文件列表。在中,我看到了可以做到这一点的方法。 由于我需要特定的扩展名,因此我创建了一个。但是,当我与此一起使用时,出现编译错误。我以为自以来,我应该能够做到这一点。代码如下: 最后一行显示编译错误: 类型的方法不适用于类型的参数 我正在尝试使用,不是。为何编译器无法识别这一点? 如果我编写自己的扩展筛选器,则此方法有效。我宁愿使用而不愿自己写。我究竟做

  • 问题内容: 我正在尝试在我的watchKit应用中使用firebase数据库。我已经在我的iPhone应用程序上开发了此功能,但是发现在我的Watch应用程序上很难做到这一点。当我尝试将firebase导入watch应用程序的VC类中时,它正在创建error 。 可以在Watch app中使用Firebase吗? 问题答案: 可悲的是,没有支持,并由于这样的事实,有没有支持在这些版本中,并高度依赖

  • 问题内容: 当请求来自Ajax.ActionLink(使用Http方法发布)时,是否可以在控制器操作上使用ValidateAntiForgeryToken属性。替代方法似乎是手动滚动JQuery Ajax请求,但我很好奇MVC Ajax框架中是否有办法。 问题答案: 我还没看过。您必须将令牌放入POST中记录的数据中。每次都使用相同的防伪令牌ID(或名称,我不记得了),但是您必须非常小心,并确保您

  • 问题内容: 最近,我开始与路由器一起使用来构建应用程序。 我通常将use 用于依赖项和代码管理。但是,当我尝试包含包含语法的文件时会出现问题。 这就是我目前所拥有的: 如何将IndexComponent放在其自己的文件中并在此文件中调用它?我尝试了通常的方法(与骨干和反应相同),但是由于语法错误。 问题答案: 所以我自己弄清楚了。 我从此仓库获得了必要的文件和说明:jsx- requirejs-p

  • 所以我一定是错过了什么,我希望执行一个语句块,如果可选的存在,否则抛出异常。 如果不是null,则打印hellow world。如果是,则抛出运行时异常。