Czy funkcje lambda mogą być wzorowane?

230

Czy w C ++ 11 można utworzyć szablon funkcji lambda? Czy może jest zbyt specyficzny, aby można go było zastosować w szablonie?

Rozumiem, że zamiast tego mogę zdefiniować klasyczną szablonową klasę / funktor, ale pytanie brzmi bardziej: czy język pozwala na tworzenie szablonów funkcji lambda?

Klaim
źródło
Czy istnieje przypadek użycia, w którym użyteczny byłby szablon lambda?
James McNellis,
7
James: Możesz zbudować funkcję iteracji po krotce (niekoniecznie przydatne).
Joe D
Pomyślałem o tym podczas czytania wywiadu z Stroustrupem mówiącym o złożoności meta-szablonów jako problemu. Gdyby było to dozwolone, wyobrażałem sobie kod-fu ninja, który może być wymyślony przez zbyt sprytnych programistów bawiących się tą kombinacją funkcji ...
Klaim

Odpowiedzi:

181

AKTUALIZACJA 2018: C ++ 20 zostanie dostarczony z szablonami i konceptualizowanymi lambdami. Funkcja została już zintegrowana ze standardową wersją roboczą.


AKTUALIZACJA 2014: C ++ 14 został wydany w tym roku i teraz zapewnia polimorficzne lambdy z taką samą składnią jak w tym przykładzie. Niektóre duże kompilatory już to implementują.


Na tym stoi (w C ++ 11), niestety nie. Polimorficzne lambdy byłyby doskonałe pod względem elastyczności i mocy.

Pierwotnym powodem, dla którego ostatecznie stały się monomorficzne, były koncepcje. Pojęcia utrudniały sytuację w tym kodzie:

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

W szablonie z ograniczeniami możesz wywoływać tylko inne szablony z ograniczeniami. (W przeciwnym razie nie można sprawdzić ograniczeń.) Czy można foowywołać bar(x)? Jakie ograniczenia ma lambda (parametr to przecież tylko szablon)?

Koncepcje nie były gotowe na rozwiązanie tego rodzaju problemów; wymagałoby to więcej rzeczy late_check( np . koncepcja nie była sprawdzana do momentu wywołania) i innych. Łatwiej było porzucić to wszystko i trzymać się monomorficznych lambd.

Jednak po usunięciu pojęć z C ++ 0x polimorficzne lambdy ponownie stają się prostą propozycją. Nie mogę jednak znaleźć żadnych propozycji. :(

GManNickG
źródło
5
Proste ... z wyjątkiem chęci ponownego wprowadzenia koncepcji i unikania funkcji, które je komplikują.
6
Myślę, że wolałbym mieć polimorficzne lambdy niż koncepcje. Nie rozumiem, jak przykład cokolwiek motywuje; możesz po prostu zabronić tego jako błędu i wymagać, aby lambda była monomorficzna [] (T x) {} lub szablon z ograniczonym szablonem [] <Ograniczenie T> (T x) {}, które można zweryfikować statycznie, aby dopasować. Czy jest jakiś powód, dla którego nie było to możliwe?
DrPizza
13
Nie musisz wybierać między koncepcjami a polimorficznymi lambdami
Dave Abrahams
3
Oto propozycja polimorficznych lambd : open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3418.pdf i implementacja zabawek w clang: faisalv.github.com/clang-glambda
Radif Sharafullin
18
Polimorficzne Lambdas będą w C ++ 14, przynajmniej są już w Community Draft :)
Arne Mertz
37

C ++ 11 lambda nie może być szablonowane, jak podano w innych odpowiedziach, ale decltype()wydaje się, że pomaga, gdy używa się lambda w obrębie klasy lub funkcji opartej na szablonie.

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

Wydruki:

My string
1024
1

Odkryłem, że ta technika jest pomocna podczas pracy z kodowanym szablonem, ale zdaję sobie sprawę, że nadal oznacza to, że same lambdy nie mogą być szablonowane.

Joel
źródło
26
Tdziałałby dobrze zamiast decltype(t)w tym przykładzie.
user2023370,
26

W C ++ 11 funkcje lambda nie mogą być szablonowane, ale w następnej wersji standardu ISO C ++ (często nazywanej C ++ 14) ta funkcja zostanie wprowadzona. [Źródło]

Przykład użycia:

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

Zauważ, że chociaż w składni użyto słowa kluczowego auto, odliczenie typu nie użyje reguł autodedukcji typu, ale zamiast tego użyje reguł dedukcji argumentu szablonu. Zobacz także propozycję ogólnych wyrażeń lambda (i jej aktualizację ).

Timo Türschmann
źródło
5
Reguły autodedukcji typu są zdefiniowane tak, aby były takie same jak reguły dedukcji templateargumentów funkcji.
underscore_d 17.04.16
10

Wiem, że to pytanie dotyczy C ++ 11. Jednak dla tych, którzy wyszukali i wylądowali na tej stronie, szablony lambdas są teraz obsługiwane w C ++ 14 i noszą nazwę Generic Lambdas.

[informacje] Większość popularnych kompilatorów obsługuje teraz tę funkcję. Obsługuje Microsoft Visual Studio 2015. Clang obsługuje. Obsługuje GCC.

Baran
źródło
6

Zastanawiam się co z tym:

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

Użyłem podobnego kodu, aby wygenerować szablon i zastanawiać się, czy kompilator zoptymalizuje funkcję „zawijania”.

przetrząsać
źródło
2
Jaki kompilator? Czy to
NicoBerrogorry
3

Istnieje rozszerzenie gcc, które pozwala na szablony 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);
                          }
              );

gdzie _widgetsjeststd::tuple< fusion::pair<Key_T, Widget_T>... >

użytkownik6559931
źródło
FWIW, ta stała się standardową składnią w C ++ 20.
LF
2

Bawiłem się najnowszą version 5.0.1kompilacją clang z -std=c++17flagą i teraz jest pewne wsparcie dla parametrów auto type dla lambdas:

#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;
}
Doug Coburn
źródło
1

Oto jedno rozwiązanie, które polega na zawinięciu lamby w strukturę:

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

      lam();                                                            
   }                                                                    
};   

Aby użyć:

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

Głównym problemem z tym (oprócz dodatkowego pisania) jest to, że nie możesz osadzić tej definicji struktury w innej metodzie lub dostajesz (gcc 4.9)

error: a template declaration cannot appear at block scope

Próbowałem też to zrobić:

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

Z nadzieją, że mógłbym to wykorzystać w ten sposób:

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

Ale pojawia się błąd kompilatora:

error: lambda-expression in unevaluated context

Więc to nie działa ... ale nawet gdyby się skompilowało, miałoby to ograniczone zastosowanie, ponieważ nadal musielibyśmy umieścić „używanie LamdaT” w zakresie plików (ponieważ jest to szablon), co w pewnym sensie nie spełnia celu lambdas.

rmccabe3701
źródło
1

Nie jestem pewien, dlaczego nikt inny tego nie sugerował, ale możesz napisać funkcję szablonową, która zwraca funkcje lambda. Poniższe rozwiązało mój problem, powód, dla którego przyszedłem na tę stronę:

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

Teraz, gdy chcę funkcji, która przyjmuje dany typ argumentów (np. std::string), Po prostu mówię

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

i teraz f("any string")wraca 1.0.

To jest przykład tego, co rozumiem przez „wzorcową funkcję lambda”. (Ten szczególny przypadek jest używany do automatycznego zapewniania obojętnej funkcji ważenia, gdy ktoś nie chce ważić swoich danych, niezależnie od ich danych.)

Jim Pivarski
źródło
2
Działa to tylko wtedy, gdy znasz typ argumentu lambda przed utworzeniem lambda, w którym to przypadku możesz po prostu użyć lambdy o określonym typie jako argumentu. Celem polimorficznej lambda jest zapewnienie pracy do wykonania na typie argumentu, którego nigdy nie znasz podczas pisania kodu pracy. Zasadniczo jest to zupełnie inne i dlatego nie zostało zasugerowane.
Klaim
Ach, racja, rozumiem. Nie myślałem o tym przypadku użycia --- myślę o funkcjach lambda jako o „w locie” i o tym rodzaju polimorfizmu jako o czymś w bibliotece uniwersalnej. Pisałem bibliotekę szablonów, która musi akceptować funkcje lambda użytkownika dowolnego typu, a także zapewniać wartości domyślne odpowiedniego typu.
Jim Pivarski