从lambda继承是什么意思

What does it mean to inherit from lambda?

本文关键字:意思 是什么 继承 lambda      更新时间:2023-10-16

找到了看起来很有趣的代码:

auto a = [](){};
class B : decltype(a)
{
};

我想知道它的作用。这能以任何方式有用吗?

好吧,该代码将编译,但问题是您将无法 default 构造该类的任何对象 1 ,因为lambda 的构造函数无法访问(除了复制/移动构造函数)lambda类型保证的唯一构造函数是A 默认复制/移动构造函数。而且没有默认的构造函数

[expr.prim.lambda/21]

与lambda-expression关联的闭合类型没有默认 构造函数和删除的副本分配运算符。它有一个默认 复制构造函数和默认的移动构造函数([class.copy])。[ 注意:这些特殊成员功能被隐式定义为往常 因此可能被定义为已删除。 - 终注]

或来自cppreference:

//ClosureType() = delete;                     //(until C++14)
ClosureType(const ClosureType& ) = default;   //(since C++14)
ClosureType(ClosureType&& ) = default;        //(since C++14)

Lambda构造函数的历史无法访问可以追溯到其早期的提案,此处可以找到

在第3节,第二段,我引用:

在此翻译中,__some_unique_name是一个新名称,未使用 该计划的其他地方以与其冲突的方式 用作封闭类型。这个名字,以及班级的构造函数, 不需要接触用户 - 用户唯一的功能 可以在封闭类型中依靠的是复制构造函数(然后 构造函数如果该提案获得批准)和功能调用 操作员。关闭类型不需要默认构造函数,分配 运算符或任何其他访问功能呼叫的手段。它可能 值得实施的性能禁止创建派生类 从封闭类型。...

您可以看到,该提案甚至建议禁止创建封闭类型的派生类。


1  当然,您可以使用a复制基类,以便初始化类型B的对象。看到这个


现在,到您的问题:

这可以以任何方式有用吗?

不是您的确切形式。您的内容只能与实例a进行即时。但是,如果您从诸如lambda类型等通用可可类中继承,我可以想到两种情况。

  1. 创建一个函子,该函数在给定的继承序列中调用一组函子:

    一个简化的示例:

    template<typename TFirst, typename... TRemaining>
    class FunctionSequence : public TFirst, FunctionSequence<TRemaining...>
    {
        public:
        FunctionSequence(TFirst first, TRemaining... remaining)
            : TFirst(first), FunctionSequence<TRemaining...>(remaining...)
        {}
        template<typename... Args>
        decltype(auto) operator () (Args&&... args){
            return FunctionSequence<TRemaining...>::operator()
                (    TFirst::operator()(std::forward<Arg>(args)...)     );
        }
    };
    template<typename T>
    class FunctionSequence<T> : public T
    {
        public:
        FunctionSequence(T t) : T(t) {}
        using T::operator();
    };
    
    template<typename... T>
    auto make_functionSequence(T... t){
        return FunctionSequence<T...>(t...);
    }
    

    示例用法:

    int main(){
        //note: these lambda functions are bug ridden. Its just for simplicity here.
        //For correct version, see the one on coliru, read on.
        auto trimLeft = [](std::string& str) -> std::string& { str.erase(0, str.find_first_not_of(' ')); return str; };
        auto trimRight = [](std::string& str) -> std::string& { str.erase(str.find_last_not_of(' ')+1); return str; };
        auto capitalize = [](std::string& str) -> std::string& { for(auto& x : str) x = std::toupper(x); return str; };
        auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);
        std::string str = " what a Hullabaloo     ";
        std::cout << "Before TrimAndCapitalize: str = "" << str << ""n";
        trimAndCapitalize(str);
        std::cout << "After TrimAndCapitalize:  str = "" << str << ""n";
        return 0;
    }
    

    输出

    Before TrimAndCapitalize: str = " what a Hullabaloo     "
    After TrimAndCapitalize:  str = "WHAT A HULLABALOO"
    

    看到它活在Coliru

  2. 创建一个用重载的operator()(...)创建一个函子,并用所有基类的operator()(...)

    重载
    • 尼尔·弗里德曼(Nir Friedman)在回答这个问题的回答中已经给出了一个很好的例子。
    • 我还起草了一个类似且简化的例子,从他的身上得出。在Coliru上看到它
    • 杰森·卢卡斯(Jason Lucas)在他的CPPCON 2014演讲"与工会的多态性"中展示了其实际应用。您可以在此处找到回购,这是源代码中的确切位置之一(感谢Cameron Dacamara)

另一个很酷的技巧:因为make_functionSequence(...)的结果类型是可可类。您可以在以后的时间附加更多lambda或可召唤。

    //.... As previously seen
    auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);
    auto replace = [](std::string& str) -> std::string& { str.replace(0, 4, "Whaaaaat"); return str; };
    //Add more Functors/lambdas to the original trimAndCapitalize
    auto replaced = make_functionSequence(trimAndCapitalize, replace /*, ... */);
    replaced(str2);

lambdas是下面的功能对象,带有其他语法糖。a对类似的内容进行评估(MyLambda名称为随机名称,就像您制作namespace {}-名称名称时一样):

class MyLambda {
public:
    void operator()() {
    }
}

因此,当您从lambda继承时,您正在做的是从匿名类/结构继承。

至于有用性,它与任何其他继承一样有用。您可以在一个对象中具有多个继承的多个lambdas的功能,可以向其添加新方法以扩展它。目前我想不出任何真正的应用程序,但是我敢肯定有很多。

请参阅此问题以获取更多信息。

这实际上可能非常有用,但这取决于您想对整个事情有多指导。考虑以下代码:

#include <boost/variant.hpp>
#include <iostream>
#include <string>
#include <unordered_map>
template <class R, class T, class ... Ts>
struct Inheritor : public  T, Inheritor<R, Ts...>
{
  using T::operator();
  using Inheritor<R, Ts...>::operator();
  Inheritor(T t, Ts ... ts) : T(t), Inheritor<R, Ts...>(ts...) {}
};
template <class R, class T>
struct Inheritor<R, T> : public boost::static_visitor<R>, T
{
  using T::operator();
  Inheritor(T t) : T(t) {}
};
template <class R, class V, class ... T>
auto apply_visitor_inline(V& v, T ... t)
{
  Inheritor<R, T...> i(t...);
  return boost::apply_visitor(i, v);
}
int main()
{
  boost::variant< int, std::string > u("hello world");
  boost::variant< int, std::string > u2(5);
  auto result = apply_visitor_inline<int64_t>(u, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
  auto result2 = apply_visitor_inline<int64_t>(u2, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
  std::cout << result;
  std::cout << result2;
}

您问题中的摘要不会以任何地方的精确形式显示。但是您可以看到在apply_visitor_inline中推断了Lambdas的类型。然后,将一个从所有这些lambdas继承的类实例化。目的?出于apply_visitor之类的目的,我们能够将多个lambdas合并为一个。此功能希望接收一个单个函数对象,该对象定义了多个operator()并根据过载来区分它们。但是有时定义在我们必须覆盖的每种类型上运行的lambda更方便。在这种情况下,lambdas的继承提供了一种结合的机制。

我从这里得到了Inline访问者的想法:https://github.com/exclipy/inline_variant_visitor,尽管我没有查看那里的实现,所以此实现是我自己的(但我猜它非常相似)。<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

编辑:最初发布的代码仅由于clang中的错误而起作用。根据这个问题(C 中的Lambdas和Clang和GCC之间的差异),基本类中多个operator()的查找是模棱两可的,实际上我最初发布的代码在GCC中没有编译。两者中的新代码编译,应该合规。可悲的是,似乎没有办法使用语句进行变异,因此必须使用递归。