lambda 函数可以模板化吗?

Can lambda functions be templated?

提问人:Klaim 提问时间:8/26/2010 最后编辑:Peter MortensenKlaim 更新时间:11/17/2023 访问量:175278

问:

在 C++ 11 中,有没有办法对 lambda 函数进行模板化?还是它本质上太具体而无法模板化?

我知道我可以定义一个经典的模板化类/函子,但问题更像是:该语言是否允许模板化 lambda 函数?

C++ 模板 Lambda C++11

评论

0赞 James McNellis 8/26/2010
是否有 lambda 模板有用的用例?
12赞 Joe D 8/27/2010
James:你可以构建一个函数来迭代一个元组(不一定有用)。
0赞 Klaim 8/27/2010
我在阅读 Stroustrup 的采访时想到了这个想法,他谈到元模板的复杂性是一个问题。如果允许的话,我想象着忍者代码,它可能是由太聪明的程序员发明的,玩这个功能组合......
2赞 Johan Boulé 6/12/2021
看到十年前的历史观点很有趣。
0赞 Klaim 6/17/2021
事实上。现在我明白了这有什么用处。我也可以看到它是如何被滥用的,但所有有用的工具都可以被滥用。:做鬼脸:

答:

218赞 GManNickG 8/27/2010 #1

2018 年更新:C++20 将附带模板化和概念化的 lambda。该功能已集成到标准草案中。


2014 年更新:C++14 已于今年发布,现在提供与此示例中相同的语法的多态 lambda。一些主要的编译器已经实现了它。


目前(在 C++ 11 中),可惜没有。多态 lambda 在灵活性和功率方面非常出色。

它们最终成为单态的最初原因是因为概念。概念使这种代码情况变得困难:

template <Constraint T>
void foo(T x)
{
    auto bar = [](auto x){}; // imaginary syntax
}

在约束模板中,只能调用其他约束模板。(否则,无法检查约束。可以调用吗?lambda 有什么约束(毕竟它的参数只是一个模板)?foobar(x)

Concepts 还没有准备好处理这种事情;它需要更多的东西,比如(在调用之前不会检查概念)之类的东西。更简单的就是放弃一切,坚持使用单态 lambda。late_check

然而,随着C++0x概念的删除,多态lambda再次成为一个简单的命题。但是,我找不到任何建议。:(

评论

5赞 11/8/2010
简单。。。除了希望重新引入概念并避免使它们变得复杂的功能。
6赞 DrPizza 7/14/2011
我想我宁愿有多态的 lambda 而不是概念。我不明白这个例子是如何激励任何事情的;您可以简单地将其作为错误禁止,并要求 lambda 是单态 [](T x) {} 或约束模板 []template<Constraint T>(T x) {},可以静态验证以匹配。有什么原因导致这是不可能的吗?
13赞 Dave Abrahams 12/22/2011
您不必在概念和多态 lambda 之间做出选择:cpp-next.com/archive/2011/12/a-breakthrough-for-concepts
3赞 Radif Sharafullin 1/11/2013
以下是多态 lambdas: open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3418.pdf 和 clang 中的玩具实现的建议:faisalv.github.com/clang-glambda
18赞 Arne Mertz 5/8/2013
多态 Lambda 将在 C++14 中出现,至少它们现在在社区草案中:)
3赞 usta 9/5/2010 #2

看看 Boost.Phoenix 的多态 lambdas: 顺便说一下,http://www.boost.org/doc/libs/1_44_0/libs/spirit/phoenix/doc/html/index.html 不需要 C++0x:)

评论

2赞 Klaim 9/5/2010
我已经知道了,但无论如何,问题恰恰是关于新标准的;)
0赞 usta 9/5/2010
好的:)不幸的是,C++0x lambda 是单态的,无法模板化。
8赞 ted 12/20/2011 #3

我想知道这个怎么办:

template <class something>
inline std::function<void()> templateLamda() {
  return [](){ std::cout << something.memberfunc() };
}

我使用类似的代码来生成一个模板,并想知道编译器是否会优化“包装”函数。

评论

3赞 NicoBerrogorry 8/15/2018
什么编译器?是吗?
39赞 Joel 8/9/2013 #4

C++11 lambda 不能像其他答案中所述进行模板化,但在模板化类或函数中使用 lambda 时似乎会有所帮助。decltype()

#include <iostream>
#include <string>

using namespace std;

template<typename T>
void boring_template_fn(T t){
    auto identity = [](decltype(t) t){ return t;};
    std::cout << identity(t) << std::endl;
}

int main(int argc, char *argv[]) {
    std::string s("My string");
    boring_template_fn(s);
    boring_template_fn(1024);
    boring_template_fn(true);
}

指纹:

My string
1024
1

我发现这种技术在使用模板化代码时很有帮助,但意识到它仍然意味着 lambda 本身不能被模板化。

评论

37赞 user2023370 11/9/2015
T可以代替这个例子。decltype(t)
29赞 Timo Türschmann 11/18/2013 #5

在 C++11 中,lambda 函数无法模板化,但在下一版本的 ISO C++ 标准(通常称为 C++14)中,将引入此功能。 [查看模板]

使用示例:

auto get_container_size = [] (auto container) { return container.size(); };

请注意,尽管语法使用关键字 ,但类型推导不会使用类型推演规则,而是使用模板参数推导规则。另请参阅通用 lambda 表达式的建议(以及对此的更新)。autoauto

评论

5赞 underscore_d 4/18/2016
类型推导的规则被明确定义为与函数参数推导的规则相同。autotemplate
10赞 Ram 12/31/2014 #6

我知道这个问题是关于C++11的。但是,对于那些在谷歌上搜索并登陆此页面的人来说,C++ 14 现在支持模板化的 lambda,并且名称为 Generic Lambdas。

[信息]现在,大多数流行的编译器都支持此功能。Microsoft Visual Studio 2015 支持。Clang 支持。GCC 支持。

1赞 rmccabe3701 8/5/2015 #7

下面是一个解决方案,涉及将 lamba 包装在结构中:

template <typename T>                                                   
struct LamT                                                             
{                                                                       
   static void Go()                                                     
   {                                                                    
      auto lam = []()                                                   
      {                                                                 
         T var;                                                         
         std::cout << "lam, type = " << typeid(var).name() << std::endl;
      };                                                                

      lam();                                                            
   }                                                                    
};   

要使用,请执行:

LamT<int>::Go();  
LamT<char>::Go(); 
#This prints 
lam, type = i
lam, type = c

这样做的主要问题(除了额外的类型)你不能将这个结构定义嵌入到另一个方法中,否则你会得到(gcc 4.9)

error: a template declaration cannot appear at block scope

我也尝试过这样做:

template <typename T> using LamdaT = decltype(                          
   [](void)                                                          
   {                                                                 
       std::cout << "LambT type = " << typeid(T).name() << std::endl;  
   });

希望我能这样使用它:

LamdaT<int>();      
LamdaT<char>();

但是我收到编译器错误:

error: lambda-expression in unevaluated context

所以这是行不通的......但即使它确实编译了,它也是有限的 use,因为我们仍然必须将“using LamdaT”放在文件范围内 (因为它是一个模板),这在某种程度上违背了 lambda 的目的。

2赞 Jim Pivarski 4/27/2016 #8

我不确定为什么没有其他人提出这个建议,但您可以编写一个返回 lambda 函数的模板化函数。以下解决了我的问题,我来到此页面的原因:

template <typename DATUM>
std::function<double(DATUM)> makeUnweighted() {
  return [](DATUM datum){return 1.0;};
}

现在,每当我想要一个接受给定类型参数的函数(例如 ),我只需说std::string

auto f = makeUnweighted<std::string>()

现在返回 .f("any string")1.0

这就是我所说的“模板化 lambda 函数”的一个例子。(这种特殊情况用于在某人不想对其数据进行加权时自动提供惰性加权函数,无论他们的数据可能是什么。

评论

2赞 Klaim 4/28/2016
仅当您在创建 lambda 之前知道 lambda 参数的类型时,这才有效,在这种情况下,您可以只使用具有特定类型的 lambda 作为参数。多态 lambda 的要点是提供在编写工作代码时您永远不知道的参数类型上要完成的工作。基本上,这是完全不同的,这就是为什么没有建议的原因。
0赞 Jim Pivarski 4/28/2016
啊,对了,明白了。我没有想到那个用例---我认为 lambda 函数是动态的东西,而这种多态性是多用途库中的东西。我正在编写一个模板化库,该库需要接受用户的任何类型的 lambda 函数,并提供正确类型的默认值。
5赞 user6559931 7/7/2016 #9

有一个 gcc 扩展允许 lambda 模板

// create the widgets and set the label
base::for_each(_widgets, [] <typename Key_T, typename Widget_T>
                         (boost::fusion::pair<Key_T, Widget_T*>& pair) -> void {
                             pair.second = new Widget_T();
                             pair.second->set_label_str(Key_T::label);
                          }
              );

其中 is a_widgetsstd::tuple< fusion::pair<Key_T, Widget_T>... >

评论

1赞 L. F. 6/10/2019
FWIW,这已成为 C++20 中的标准语法。
4赞 Doug Coburn 2/21/2018 #10

我一直在使用最新的 clang 编译标志,现在对 lambda 的自动类型参数有一些很好的支持:version 5.0.1-std=c++17

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    auto slice = [](auto input, int beg, int end) {
        using T = decltype(input);
        const auto size = input.size();
        if (beg > size || end > size || beg < 0 || end < 0) {
            throw std::out_of_range("beg/end must be between [0, input.size())");
        }
        if (beg > end) {
            throw std::invalid_argument("beg must be less than end");
        }
        return T(input.begin() + beg, input.begin() + end);
    };
    auto v = std::vector<int> { 1,2,3,4,5 };
    for (auto e : slice(v, 1, 4)) {
        std::cout << e << " ";
    }
    std::cout << std::endl;
}
80赞 shilch 7/16/2020 #11

在 C++20 中,可以使用以下语法实现此操作:

auto lambda = []<typename T>(T t){
    // do something
};

评论

74赞 Kostas 10/30/2020
这是否意味着我们现在将能够编写这样的 lambda ?[]<>(){}
24赞 Arda 3/11/2021
如果您需要在调用 lambda 时显式指定模板参数,我发现这在我的使用案例中很常见(例如,如果上面的示例中不存在该参数),那么您可以使用以下语法执行此操作:.有关详细信息,请参阅 stackoverflow.com/questions/49392738T tlambda.template operator()<int>()
3赞 tartaruga_casco_mole 3/16/2022
@Arda为什么我们不能有东西.这有技术限制吗?lambda<int>()
4赞 shilch 3/18/2022
@tartaruga_casco_mole 是一个函数对象。要使用模板尖括号,您需要一个类型(或 constexpr 模板)。我假设根据规范,编译器必须将示例中的 视为对象上的小于运算符,这显然会失败。未来的 C++ 版本可能允许以这种方式调用模板。lambda<lambdaoperator()
0赞 Ryan McCampbell 9/7/2023
@tartaruga_casco_mole有趣的是,如果确实有效,则可以制作模棱两可的语法:可以是 或lambda<a>(b)lambda.operator()<a>(b)lambda.operator<(a).operator>(b)
2赞 muaz 8/6/2021 #12

C++11 的另一个解决方法是定义模板函数并将其包装在 lambda 表达式中。然而;这需要为不同的模板化 lambda 定义一个新函数:

struct ST{ int x; };

template<class T>
T templateFunc(T variable)
{
    return variable;
}

void func()
{
    ST st{10};
    auto lambda = [&](){return templateFunc<ST>(st);};
    auto res = lambda();
}
0赞 tuket 11/6/2023 #13

正如其他人所说,您可以在 C++20 lambda 中使用模板参数。

auto doSomething = []<typename T>(T x) {
    // blabla
};

但是,其他答案忘记告诉您如何调用这些 lambda

在前面的示例中,这很容易,因为所有模板类型都可以从提供的参数中推导出来。

doSomething();

但是,当无法从参数中推断出模板类型时,问题就来了。

auto readValue = [&buffer]<typename T>() -> T{
    return *(const T*)buffer;
};

您可能希望像常规模板化函数一样调用它:

readValue<int>(); // WRONG!

但这不会编译。你必须这样写:

readValue.template operator()<int>(); // VALID!
0赞 Chris 11/17/2023 #14

这有点冗长,但您可以将 lambda “展开”到可调用对象中,并在类定义上使用模板。您甚至可以捕获变量,像使用 lambda 一样将对象存储到变量中,或者为单个调用实例化新对象。 可能性是无穷无尽的。

template <typename T>
struct Callable
{
    Callable(int& i) : capturedInt(i) {}
    T operator()(T a, T b)
    {
        T total = a;
        for (int i = 0; i < capturedInt; i++)
            total *= b;
        return total;
    }
    private:
        int& capturedInt;
};

int main() {
    int i = 5;
    Callable<float> callable(i);
    
    cout << "Result of callable(5)(10, 1.5f): " << callable(10, 1.5f) << '\n';

    i = 6;
    cout << "Result of callable(6)(1.5f, 10): " << callable(1.5f, 10) << '\n';

    i = 10;
    cout << "Result of Callable<int>(10)(2, 3): " << Callable<int>(i)(2, 3) << '\n';

    return 0;
}

输出:

Result of callable(5)(10, 1.5f): 75.9375
Result of callable(6)(1.5f, 10): 1.5e+06
Result of Callable<int>(10)(2, 3): 118098