Подтвердить что ты не робот

Что значит унаследовать от лямбда?

Нашел этот код, который выглядит интересным:

auto a = [](){};

class B : decltype(a)
{
};

Я хочу знать, что он делает. Может ли это быть полезным в любом случае?

4b9b3361

Ответ 1

Ну, этот код будет компилироваться, но проблема в том, что вы не сможете по умолчанию построить любой объект этого класса 1 потому что конструктор лямбда недоступен (кроме копии /move ). Единственными конструкторами, гарантируемыми типом lambda, является конструктор copy/move по умолчанию. И нет конструктора по умолчанию

[expr.prim.lambda/21]

Тип замыкания, связанный с лямбда-выражением, не имеет значения по умолчанию конструктор и оператор назначения удаленной копии. Он имеет дефолт конструктор копирования и конструктор перемещения по умолчанию ([class.copy]). [ Примечание. Эти специальные функции-члены неявно определяются как обычно, и поэтому может быть определен как удаленный. - конечная нота]

или cppreference:

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

История конструктора лямбда, которая недоступна, восходит к предложению ранних дней, найдена здесь

В разделе 3, второй абзац, и я цитирую:

В этом переводе __some_unique_name - новое имя, не используется в другом месте программы таким образом, чтобы вызвать конфликты с ее использовать как тип замыкания. Это имя и конструктор для класса, не нужно подвергать пользователя - единственные функции, которые пользователь могут полагаться в типе замыкания, являются конструктором копирования (и перемещением конструктор, если это предложение одобрено) и вызов функции оператор. Для типов Closure не нужны конструкторы по умолчанию, назначение операторы или любые другие средства доступа, помимо вызовов функций. Это может быть целесообразным для возможности запретить создание производных классов от типов закрытия....

Как вы можете видеть, предложение даже предполагало, что создание производных классов из типов закрытия должно быть запрещено.


1 Конечно, вы можете скопировать-инициализировать базовый класс с помощью a, чтобы инициализировать объект типа B. См. this


Теперь, на ваш вопрос:

Может ли это быть полезным в любом случае?

Не в вашей точной форме. Ваш экземпляр будет доступен только с экземпляром a. Однако, если вы наследуете от общего класса Callable, такого как лямбда-тип, есть два случая, о которых я могу думать.

  • Создать функтор, который вызывает группу функторов в заданной последовательности наследования:

    Упрощенный пример:

    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

  • Создайте Functor с перегруженным operator()(...), перегруженным всеми базовыми классами <<29 > :

    • Нир Фридман уже дал хороший пример этому в своем ответе на этот вопрос.
    • Я также разработал аналогичный и упрощенный пример, взятый из Его. Смотрите на Coliru
    • Джейсон Лукас также продемонстрировал свои практические применения в своей презентации CppCon 2014 "Полиморфизм с Союзами" . Здесь вы можете найти Repo , одно из точного местоположения в исходном коде здесь (Спасибо Cameron DaCamara)

Еще один классный трюк: поскольку результирующий тип из make_functionSequence(...) является вызываемым классом. Вы можете добавить больше лямбда или вызывать ее позже.

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

Ответ 2

Lambdas являются объектами функции под дополнительным синтаксическим сахаром. a оценивается примерно так (с именем MyLambda, являющимся случайным именем, так же, как при создании namespace {} - имя пространства имен будет случайным):

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

Итак, когда вы наследуете лямбда, то, что вы делаете, наследуется от анонимного класса/структуры.

Что касается полезности, то он полезен, как и любое другое наследование. Вы можете иметь функциональность нескольких лямбда с множественным наследованием в одном объекте, вы можете добавить к ней новые методы, чтобы расширить его. На данный момент я не могу представить ни одного реального приложения, но я уверен, что их много.

Подробнее см. этот вопрос.

Ответ 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;
}

Фрагмент вашего вопроса не отображается в точном виде в любом месте. Но вы можете видеть, что типы лямбдов выводятся в apply_visitor_inline. Затем создается экземпляр класса, который наследуется от всех этих лямбдов. Цель? Мы можем объединить несколько ягнят в один, для целей, подобных apply_visitor. Эта функция ожидает получить один объект функции, который определяет несколько operator() и различает их на основе перегрузки. Но иногда удобнее определять лямбду, которая работает на каждом из типов, которые мы должны покрыть. В этом случае наследование от lambdas обеспечивает механизм объединения.

У меня появилась встроенная идея для посетителей: https://github.com/exclipy/inline_variant_visitor, хотя я не смотрел на реализацию там, поэтому эта реализация (но я думаю, что это очень похоже).

Изменить: первоначально опубликованный код работал только из-за ошибки в clang. Согласно этому вопросу (Перегруженные lambdas в С++ и различия между clang и gcc) поиск нескольких operator() в базовых классах неоднозначен, и действительно код, который я опубликовал изначально не компилировался в gcc. Новый код компилируется в обоих случаях и должен быть совместимым. К сожалению, нет никакого способа, по-видимому, использовать оператор с переменной версией, поэтому нужно использовать рекурсию.