【问题标题】:Lambda function argument + deduction improvementLambda函数参数+推演改进
【发布时间】:2017-05-30 09:26:41
【问题描述】:

我为必须执行很多并且过去使用宏完成的双 for 循环构建了这个模板函数:

template <typename TYPE_OF_ENTITY, typename LAMBDA_FUNC>
void foreach_t(list faces, LAMBDA_FUNC func)
{
    for(...)
        for(...)
            func(pEnt)
}

这很好用,可以调用:

foreach_t<FACE>(faces,[&](FACE* pFace){...});

然后我想确保您的 lambda 必须将 TYPE_OF_ENTITY* 作为参数。为此,我需要一个有状态和无状态的模板函数。

template <typename TYPE_OF_ENTITY, typename LAMBDA_RETURN_TYPE>
using functionPtr = LAMBDA_RETURN_TYPE(*)(TYPE_OF_ENTITY*);

template <typename TYPE_OF_ENTITY, typename LAMBDA_RETURN_TYPE>
void foreach_t(list faces, functionPtr<TYPE_OF_ENTITY, LAMBDA_RETURN_TYPE> func)
{
    for(...)
        for(...)
            func(pEnt)
}

template <typename TYPE_OF_ENTITY, typename LAMBDA_RETURN_TYPE>
void foreach_t(list faces, std::function<LAMBDA_RETURN_TYPE(TYPE_OF_ENTITY*)> func)
{
    for(...)
        for(...)
            func(pEnt)
}

它们按我的意愿工作,只是现在我必须指定 2 个模板参数,因为它们不能再被推导出来:

foreach_t<FACE, void>(faces,[&](FACE* pFace){...});

有没有办法改进这个推论,所以我只需要传递函数参数?

如果可能需要visual studio 2013 compatible v2.1 compiler支持

【问题讨论】:

    标签: c++ lambda type-deduction


    【解决方案1】:

    如果您需要做的只是检查 lambda 是否将 TYPE_OF_ENTITY* 类型的值作为参数,您实际上不需要将函数拆分为两个单独的重载(对于有状态和无状态 lambda)。您可以简单地检查是否可以使用这种参数调用它,例如:

    #include <type_traits> // for std::declval
    
    template <typename TYPE_OF_ENTITY, typename LAMBDA_FUNC>
    auto foreach_t(list faces, LAMBDA_FUNC func) -> decltype(func(std::declval<TYPE_OF_ENTITY*>()), void())
    {
        for(...)
            for(...)
                func(pEnt)
    }
    

    【讨论】:

    • 经过进一步搜索,我发现 declval 是一个东西,将对其进行测试并尝试理解它。
    • 我将对此进行一些实验以更好地理解它,但您能否解释一下您的代码的更多功能。我知道 -> decltype 构造是指定 auto 的返回类型。我怀疑 func(std::declval()) 是使用 TYPE_OF_ENTITY* 进行的调用,而 void() 是返回类型,但我看不出这是如何结合在一起来强制执行正确的返回类型的。如果 lambda 返回 void 以外的内容怎么办,这不会因为它不返回任何内容而在我的函数中引入错误吗?
    • 是的 - 这种返回类型声明称为尾随返回类型。它是 SFINAE 友好的,这意味着如果会有另一个采用不同类型参数的重载,它将被默默地拒绝,以防另一个重载更适合。 decltype 触发 sfinae 并测试其中的表达式是否正确。这里测试的表达式是func(std::declval&lt;TYPE_OF_ENTITY*&gt;()),所以如果可以使用TYPE_OF_ENTITY* 类型的值调用函数,void() 将保留foreach_t 的返回类型(void)
    【解决方案2】:

    如果您想在函数调用时指定闭包的返回类型,那么static_assert 可能会有所帮助:

    #include <utility>
    #include <vector>
    
    struct Dog {};
    
    struct list {};
    
    template <
    typename LAMBDA_RETURN_TYPE,
      typename TYPE_OF_ENTITY, 
    class F
    >
    void foreach_t(const std::vector<TYPE_OF_ENTITY*>& faces, F func)
    {
      using ret_type = decltype(func(std::declval<TYPE_OF_ENTITY*>()));
      using same_type = typename std::is_same<LAMBDA_RETURN_TYPE, ret_type>::type;
      constexpr auto ok = same_type::value;
      static_assert(ok, "");
      for(auto pdog : faces)
        func(pdog);
    }
    
    int main()
    {
      auto mydog = [](Dog* pdog) -> bool // change to int and this compiles
      { return false; };
    
      std::vector<Dog*> dogs;
      foreach_t<int>(dogs, mydog);  
    
    }
    

    【讨论】:

    • 这似乎确实减少了我想要的模板参数的数量,但@W.F.的答案似乎以更复杂但更短的方式做同样的事情。你认为这个答案比另一个有优势吗?
    • @turoni 有区别。他正在使用 SFNAE,如果您传递一个返回类型与指定类型不同的闭包,我的会显式导致程序失败。我逐行说明类型检查的事实只是风格问题。您采用哪种途径取决于您的具体要求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-25
    相关资源
    最近更新 更多