【问题标题】:What does it mean to inherit from lambda?从 lambda 继承是什么意思?
【发布时间】:2016-06-25 01:11:35
【问题描述】:

发现这段代码看起来很有趣:

auto a = [](){};

class B : decltype(a)
{
};

我想知道它的作用。这有什么用吗?

【问题讨论】:

  • 这看起来很有趣,但它不能在 GCC 6.1.0 上编译。您使用的是什么编译器/版本?
  • 我已经看到这种技术用于创建一个 overload 函数,该函数接受一堆 lambda,并将它们全部包装成一个类型,并为 lambda 列表中给出的每个参数列表使用重载函数调用运算符.
  • @chris 请问,你能提供一个实现的例子吗?听起来真的很有趣。谢谢。
  • @Cheersandhth.-Alf 它很宽泛,不太宽泛。投票终结者似乎经常误解这一点
  • @skypjack,我想我指的是this one

标签: c++


【解决方案1】:

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

[expr.prim.lambda/21]

与 lambda 表达式关联的闭包类型没有默认值 构造函数和已删除的复制赋值运算符。它有一个默认值 复制构造函数和默认的移动构造函数([class.copy])。 [ 注意:这些特殊的成员函数像往常一样被隐式定义, 因此可能被定义为已删除。 ——尾注]

或来自cppreference

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

无法访问 lambda 构造函数的历史可以追溯到早期的提案,发现 here

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

在此翻译中,__some_unique_name 是一个新名称,未使用 在程序的其他地方以一种会导致与它的冲突的方式 用作闭包类型。这个名字和类的构造函数, 不需要暴露给用户——用户唯一能看到的特征 在闭包类型中可以依赖的是一个复制构造函数(和一个移动 如果该提案获得批准,则构造函数)和函数调用 操作员。闭包类型不需要默认构造函数,赋值 运算符,或函数调用之外的任何其他访问方式。有可能 值得实现禁止创建派生类 从闭包类型。 ...

如您所见,该提案甚至建议禁止从闭包类型创建派生类。


1 当然,您可以使用a 复制初始化基类,以初始化B 类型的对象。见this


现在,回答您的问题:

这有什么用吗?

不是您的确切形式。你的只能用实例a 实例化。 但是,如果你继承自一个通用的 Callable Class,比如 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"
    

    Live on Coliru

  2. 使用重载的operator()(...) 创建一个 Functor,重载所有基类的operator()(...)

    • Nir Friedman 已经在他的answer 中针对这个问题给出了一个很好的例子。
    • 我还起草了一个类似的简化示例,取自他的。看到它on Coliru
    • Jason Lucas 在他的CppCon 2014 presentation "Polymorphism with Unions" 中也展示了它的实际应用。您可以找到 Repo here,源代码中的确切位置之一 here(感谢 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);

【讨论】:

  • 我不认为这是真的,你可以构造一个 B,它只需要一个构造函数,它接受一个 a 的实例并使用传递的实例构造它的基类。
  • @NirFriedman。你不能构造B,除非你用a复制构造基类并且只有实例a,我的意思是只有 a....你看,问题在这里是 a 已经是一个唯一类型的实例...创建的任何其他 lambda 都不能是 a 的类型
  • 我知道,这就是我认为我在原帖中所写的内容,如果不清楚,请见谅。无论如何,我认为您最初写的是 B 根本无法构造,没有任何限定,这是不正确的。似乎您将其更改为说 B 不能默认构造。这仍然不是真的,您可以编写一个使用全局 a 初始化基类的默认构造函数。
  • @NirFriedman,更新了我的答案,并为此添加了脚注:demo
  • 有一个建议让无状态 lambdas 默认可构造:open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0624r2.pdf
【解决方案2】:

Lambdas 在下面是function objects,带有额外的合成糖。 a 的计算结果是这样的(MyLambda 名称是一个随机名称,就像您创建 namespace {} 时一样 - 命名空间名称将是随机的):

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

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

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

请参阅this question 了解更多信息。

【讨论】:

    【解决方案3】:

    这实际上非常有用,但这取决于你想对整个事情有多直接。考虑以下代码:

    #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;
    }
    

    您问题中的 sn-p 在任何地方都没有以确切的形式显示。但是您可以看到,在 apply_visitor_inline 中推断出 lambda 的类型。然后实例化一个继承自所有这些 lambda 的类。目的?我们可以将多个 lambdas 组合成一个,以达到apply_visitor 之类的目的。此函数期望接收定义多个operator() 的单个函数对象,并根据重载区分它们。但有时定义一个对我们必须涵盖的每种类型进行操作的 lambda 会更方便。在这种情况下,从 lambdas 继承提供了一种组合机制。

    我从这里得到了内联访问者的想法:https://github.com/exclipy/inline_variant_visitor,虽然我没有看那里的实现,所以这个实现是我自己的(但我猜它非常相似)。

    编辑:最初发布的代码仅由于 clang 中的错误而起作用。根据这个问题(Overloaded lambdas in C++ and differences between clang and gcc),在基类中查找多个operator() 是模棱两可的,而且我最初发布的代码确实没有在 gcc 中编译。新代码在两者中编译并且应该是合规的。遗憾的是,似乎无法使用可变参数 using 语句,因此必须使用递归。

    【讨论】:

    • lambda 是copy-constructed。您需要一个已经存在的 lambda 来执行此操作。在你的代码中,你知道这部分是做什么的吗? - Inheritor(T... t) : T(t)... {} ...,尝试用一堆 lambda 替换 T ...
    • 我知道它的作用,我写了它,这只是我在别处看到的想法。 T 是 lambda 的类型,它相当于 op 在间接层后面写的内容。
    • 我依稀记得在 Herb Sutter 或 David Vandevoorde 或 Dave Abrahams 或其中两个人的博客文章中看到过这个想法,作为关于重载的一般讨论的示例。
    • 另外,更普通一点的是,允许您从闭包类型继承意味着您可以 EBO 为空的。
    猜你喜欢
    • 2020-11-04
    • 2017-02-23
    • 1970-01-01
    • 2014-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    相关资源
    最近更新 更多