这是合法的模板 lambda 语法吗?

Sim*_*ton 17 c++ lambda templates c++14

在重构一些遗留代码时,我遇到了要在 STL 算法中使用的谓词的这种传统实现:

template<bool b>
struct StructPred {
    bool operator()(S const & s) { return s.b == b; }
};
Run Code Online (Sandbox Code Playgroud)

我累了,撞到了鲍尔默峰,所以我不小心把它改写成了这样的 lambda,这看起来很自然,也很有效:

template<bool b>
auto lambda_pred = [] (S const & s) { return s.b == b; };
Run Code Online (Sandbox Code Playgroud)

后来我意识到我从未见过这样的模板 lambda。我在 cppreference 或 stackoverflow 上找不到任何类似的东西。生成模板 lambda 的规范方法似乎是将它们包装在模板结构或模板函数中。C++20 为 lambda 引入了命名模板参数,但这是一种不同的语法(在捕获括号之后)。

现在我的问题是:这是合法的语法吗?它在任何地方都有记录吗?它甚至是 lambda 还是其他什么?与包装替代品相比,是否有任何影响或副作用?为什么每个人都推荐包装器实现呢?我错过了一些明显的东西吗?

下面和Godbolt 上的完整工作测试代码。为了确保我还添加了一个类型模板参数版本。MSVC、GCC 和 clang 对这段代码很满意。

#include <vector>
#include <algorithm>

struct S {
    bool b = false;
};

// classic function object
template<bool b>
struct StructPred {
    bool operator()(S const & s) { return s.b == b; }
};

// template function producing a lambda
template<bool b>
auto make_pred() {
    return [] (S const & s) { return s.b == b; };
}

// direct template lambda
template<bool b>
auto lambda_pred = [] (S const & s) { return s.b == b; };

// also with type params
template<typename T, bool b>
auto lambda_pred_t = [] (T const & t) { return t.b == b; };

std::pair<size_t, size_t> count1(std::vector<S> const & v) {
    return {
        std::count_if(v.begin(), v.end(), StructPred<true>{}),
        std::count_if(v.begin(), v.end(), StructPred<false>{})
    };
}

std::pair<size_t, size_t> count2(std::vector<S> const & v) {
    return {
        std::count_if(v.begin(), v.end(), make_pred<true>()),
        std::count_if(v.begin(), v.end(), make_pred<false>())
    };
}

std::pair<size_t, size_t> count3(std::vector<S> const & v) {
    return {
        std::count_if(v.begin(), v.end(), lambda_pred<true>),
        std::count_if(v.begin(), v.end(), lambda_pred<false>)
    };
}

std::pair<size_t, size_t> count4(std::vector<S> const & v) {
    return {
        std::count_if(v.begin(), v.end(), lambda_pred_t<S, true>),
        std::count_if(v.begin(), v.end(), lambda_pred_t<S, false>)
    };
}

void test() {
    std::vector<S> v{3};
    v[1].b = true;
    // all implementations correctly return {1,2}
    auto c1 = count1(v);
    auto c2 = count2(v);
    auto c3 = count3(v);
    auto c4 = count4(v);
}
Run Code Online (Sandbox Code Playgroud)

Jan*_*tke 16

template<bool b>
auto lambda_pred = [] (S const & s) { return s.b == b; };
Run Code Online (Sandbox Code Playgroud)

这不是真正的模板 lambda,而是分配给 lambda的变量模板

它不等同于将模板参数添加到隐式声明的闭包中struct,该闭包将此 lambda 作为调用运算符(传统方法):

template<bool b>
struct StructPred { // NOT equivalent to this
    bool operator()(S const & s) { return s.b == b; }
};

struct StructPred { // NOT equivalent to this either
    template<bool b>
    bool operator()(S const & s) { return s.b == b; }
};
Run Code Online (Sandbox Code Playgroud)

它相当于根据变量的模板参数创建不同的闭包。因此,对于bool示例,这就像operator()在以下类型之一之间进行选择:

struct StructPred_true {
    bool operator()(S const & s) { return s.b == true; }
}

struct StructPred_false {
    bool operator()(S const & s) { return s.b == false; }
}
Run Code Online (Sandbox Code Playgroud)

这种方法不允许部分专业化,因此功能较弱。这种方法可能不受欢迎的另一个原因是它不能让您轻松访问闭包类型。 StructPred可以显式使用,不像匿名类StructPred_trueStructPred_false

C++20 中的模板 lambda 如下所示:

struct StructPred_true {
    bool operator()(S const & s) { return s.b == true; }
}

struct StructPred_false {
    bool operator()(S const & s) { return s.b == false; }
}
Run Code Online (Sandbox Code Playgroud)

这相当于使闭包的operator()模板化。

  • @Simpleton请注意,您可以对 C++20 之前的通用 lambda 使用相同的方法,从调用站点显式指定发明的 _type_ 模板参数的类型,从而覆盖这些模板参数的模板参数推导。然而,C++20 方法提供的除了(与其他通用实体)一致之外,还使用类型和非类型模板参数,这些参数不能通过模板参数推导来推导,而且可以设置为默认值,[允许例如(可以说无用)这样的代码](https://wandbox.org/permlink/dgyvNwPPgT8ub9vq)。 (2认同)
  • 然而,它相当于 `template&lt;bool b&gt; struct { bool operator()(S const &amp; s) { return sb == b; } } struct_pred;` (2认同)

dfr*_*fri 6

以下所有标准参考均指N4659:2017 年 3 月后 Kona 工作草案/C++17 DIS


通用 lambdas:C++14 特性

生成模板 lambda 的规范方法似乎是将它们包装在模板结构或模板函数中。C++20 为 lambdas 引入了命名模板参数,但这是一种不同的语法(在捕获括号之后)。

另一个答案彻底解释了 OPs 变量模板的构造,而这个答案解决了上面强调的部分;即泛型 lambdas 是 C++14 的语言特性,而不是仅在 C++20 可用的东西。

根据[expr.prim.lambda.closure]/3 [extract]:

[...] 对于泛型 lambda,闭包类型有一个公共内联函数调用运算符成员模板,其模板参数列表由一个发明的类型模板参数组成,用于autolambda 的参数声明子句中的每次出现,在出场顺序。[...]

泛型 lambda 可以声明为

auto glambda = [](auto a, auto b) { return a < b; };
Run Code Online (Sandbox Code Playgroud)

这是可比的

struct anon_struct {
    template<typename T, typename U>
    bool operator()(T a, U b) { return a < b; }
}
Run Code Online (Sandbox Code Playgroud)

并不是

template<typename T, typename U>
struct anon_struct {
    bool operator()(T a, U b) { return a < b; }
}
Run Code Online (Sandbox Code Playgroud)

这是必不可少的,因为单个通用 lambda 对象(其闭包类型实际上不是类模板而是非模板(非联合)类)可用于为其发明的模板的不同实例一般调用其函数调用运算符模板参数。

#include <iostream>
#include <ios>

int main() {
    auto gl = [](auto a, auto b) { return a < b; };
    std::cout << std::boolalpha 
        << gl(1, 2) << " "      // true ("<int, int>")
        << gl(3.4, 2.2) << " "  // false ("<double, double>")
        << gl(98, 'a');         // false ("<int, char>")
}
Run Code Online (Sandbox Code Playgroud)

具有显式模板参数列表的通用 lambda:C++20 特性

从 C++20 开始,我们可以在声明泛型 lambda时使用显式模板参数列表,以及在调用泛型 lambda时提供用于提供显式模板参数的加糖语法。

在 C++14 和 C++17 中,泛型 lambda 的模板参数只能隐式声明为 lambda 声明中每个声明参数的发明类型模板参数auto,它具有以下限制:

  • 发明的模板参数只能是合成的类型模板参数(如上所示),并且
  • 类型模板参数不能在 lambda 的主体中直接访问,但需要使用decltype相应的auto参数来提取。

或者,如一个人为的例子所示:

#include <type_traits>

// C++17 (C++14 if we remove constexpr
//        and use of _v alias template).
auto constexpr cpp17_glambda = 
    // Template parameters cannot be declared
    // explicitly, meaning only type template
    // parameters can be used.
    [](auto a, auto b) 
        // Inventend type template parameters cannot
        // be accessed/used directly.
        -> std::enable_if_t<
             std::is_base_of_v<decltype(a), decltype(b)>> {};

struct Base {};
struct Derived : public Base {};
struct NonDerived {};
struct ConvertsToDerived { operator Derived() { return {}; } };
    
int main() {
    cpp17_glambda(Base{}, Derived{});    // Ok.
    //cpp17_glambda(Base{}, NonDerived{}); // Error.
    
    // Error: second invented type template parameter
    //        inferred to 'ConvertsToDerived'.
    //cpp17_glambda(Base{}, ConvertsToDerived{});
    
    // OK: explicitly specify the types of the invented
    //     type template parameters.
    cpp17_glambda.operator()<Base, Derived>(
        Base{}, ConvertsToDerived{});
}
Run Code Online (Sandbox Code Playgroud)

现在,在 C++20 中,引入了 lambdas(以及 requires 子句)的名称模板参数,上面的示例可以简化为:

#include <type_traits>

auto constexpr cpp20_glambda = 
    []<typename T, typename U>(T, U) 
        requires std::is_base_of_v<T, U> { };

struct Base {};
struct Derived : public Base {};
struct NonDerived {};
struct ConvertsToDerived { operator Derived() { return {}; } };

int main() {
    cpp20_glambda(Base{}, Derived{});    // Ok.
    //cpp20_glambda(Base{}, NonDerived{}); // Error.
    
    // Error: second type template parameter
    //        inferred to 'ConvertsToDerived'.
    //cpp20_glambda(Base{}, ConvertsToDerived{});
    
    // OK: explicitly specify the types of the
    //     type template parameters.
    cpp20_glambda.operator()<Base, Derived>(
        Base{}, ConvertsToDerived{});
}
Run Code Online (Sandbox Code Playgroud)

此外,我们还可以使用模板参数声明 lambda,而这些模板参数不一定是类型模板参数:

#include <iostream>
#include <ios>

template<typename T>
struct is_bool_trait {
    static constexpr bool value = false;  
};

template<>
struct is_bool_trait<bool> {
    static constexpr bool value = true;  
};

template<typename T>
struct always_true_trait {
    static constexpr bool value = true;    
};

int main() {
    auto lambda = []<
        template<typename> class TT = is_bool_trait>(auto a) -> bool { 
        if constexpr (!TT<decltype(a)>::value) {
            return true;  // default for non-bool. 
        }
        return a; 
    };
    std::cout << std::boolalpha 
        << lambda(false) << " "                            // false
        << lambda(true) << " "                             // true
        << lambda(0) << " "                                // true
        << lambda(1) << " "                                // true
        << lambda.operator()<always_true_trait>(0) << " "  // false
        << lambda.operator()<always_true_trait>(1);        // true
}
Run Code Online (Sandbox Code Playgroud)