从lambda继承是什么意思?

时间:2016-06-25 01:11:35

标签: c++

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

component.options.props = {
    size: Number,
    type: {
      type: Number,
      required: false,
      twoWay: false
 }

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

3 个答案:

答案 0 :(得分:35)

那么,代码会编译,但问题是你将无法默认构造该类 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节第2段中,我引用:

  

在此翻译中,__some_unique_name是一个新名称,未使用   程序中的其他地方可能会导致与其冲突   用作封闭类型。这个名称,以及该类的构造函数,   不需要向用户公开 - 用户的唯一功能   可以依赖于闭包类型的是一个复制构造函数(和一个移动   构造函数(如果该提议被批准)和函数调用   运营商。闭包类型不需要默认构造函数,赋值   运算符,或超出函数调用的任何其他访问方式。有可能   值得实现以禁止创建派生类   从封闭类型。 ...

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

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

现在,问题:

  

这可以用于任何方式吗?

不是您的确切形式。您只能使用实例a进行实例化。 但是,如果从一个普通的Callable类继承,例如lambda类型,我可以想到两种情况。

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

    简化示例:

    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()(...)

  3. 另一个很酷的技巧:因为make_functionSequence(...)的结果类型是一个可调用的类。你可以在以后附加更多的lambda或callable。

        //.... 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);
    

答案 1 :(得分:8)

Lambdas下面是function objects,还有额外的合成糖。 a评估为类似的名称(MyLambda名称是随机名称,就像您创建namespace {}时一样 - 命名空间名称将是随机的):

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

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

至于有用性,它和任何其他继承一样有用。您可以在一个对象中具有多个具有多重继承的lambdas的功能,您可以向其添加新方法以扩展它。我现在无法想到任何真正的应用,但我确定有很多应用。

有关详细信息,请参阅this question

答案 2 :(得分:6)

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

#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中推断出lambda的类型。然后实例化一个继承自所有这些lambdas的类。目的?我们能够将多个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中编译。新代码在两者中编译并且应该是兼容的。遗憾的是,似乎没有办法使用可变参数使用语句,因此必须使用递归。