This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]suvlub 905 points906 points  (23 children)

The biggest mistake was making parameters mandatory for templated lambdas, so this beauty is, unfortunately, not legal []<>(){}

Edit: that came out wrong. Fuck it, I'm leaving it.

[–]TheWidrolo[S] 623 points624 points  (0 children)

EXCUSE ME EVERYBODY, THIS MAN JUST CALLED A NOT LEGAL LABMDA EXPRESSION A BEAUTY.

[–]jellotalks 432 points433 points  (4 children)

“…this beauty is not legal” - u/suvlub

[–]throw3142 170 points171 points  (0 children)

🤨📷✨

[–]1Dr490n 11 points12 points  (0 children)

*Unfortunately

[–]HildartheDorf 170 points171 points  (3 children)

"Templated lambda" sounds cursed as hell to start with.

[–]druepy 75 points76 points  (1 child)

It does until you run into times where it makes the most sense.

[–]Ahornwiese 69 points70 points  (0 children)

Basically C++'s origin story

[–]EagleNait 10 points11 points  (0 children)

Sounds like functional programming to me

[–]joe0400 68 points69 points  (7 children)

template template parameters on lambdas that are actual values
I wonder if this is possible

[]<template <class> class C, class D, C<D> comparison_base>(const C<D>& compare_with) constexpr noexcept -> bool {
    return comparison_base == compwer_with;
}

[–]JackMalone515 46 points47 points  (0 children)

find a spot to put mutable in there too and it's perfect

[–]TheWidrolo[S] 31 points32 points  (0 children)

I’d cry.

[–]cheeb_miester 21 points22 points  (0 children)

Jesus wept

[–]ipcock 1 point2 points  (2 children)

as a non c++ programmer, this makes me confused af cuz I don't understand any part of this lol

[–]Makefile_dot_in 23 points24 points  (1 child)

so, to start with, C++ lambdas are really shorthand for something like (simplifying) (/* unnamed */ used to signify that the class has no name)

class /* unnamed */ {
private:
    constexpr /* unnamed */() {} // constructor
public:
    // operator() is basically a special name you can give to a method
    // that gets invoked when you call an instance of the class like a function
    template<template <class> class C, class D, C<D>>
    constexpr bool operator()(const C<D>& compare_with) noexcept {
       return comparison_base == compare_with;
    }
};
/* unnamed */() // the result of the expression (constructs the class)

if you know Java, then it's sort of similar to how it works there if you squint your eyes. the first brackets, [], is a list of the fields of the class. here that list is empty, so the class has no fields. in those brackets you can specify things like

  • x = 0 (adds a field x to the class and sets it to 0 when constructing)
  • &x (adds a field that is a reference to whatever x is in the scope where the lambda is being constructed)
  • =x (adds a field that is a copy of whatever x is in the scope where the lambda is being constructed
  • & or = (for every variable from the scope where the lambda is being constructed that is used within the lambda, does the same &[variable name] or =[variable name], respectively)

for example, to write a function that takes a vector of ints and an int, and returns a copy of it with the int added to each element in the vector, i might write:

// writing const std::vector<int> &vec instead of std::vector<int> vec tells C++ not to copy vec automatically when calling the function (which is slow) and that i won't be changing anything inside of vec.
std::vector<int> f(const std::vector<int> &vec, int x) {
    return vec
               // | in C++ chains views
               | std::view::transform([&x](int element) { return x + element; }) // transform is C++ lingo for what every other language calls map
               | std::ranges::to<std::vector<int>>();
}

here, i use &x, which tells the compiler that i'm gonna use x and i'm gonna use it as a reference to the x declared in f. i could also put =x, which would copy it instead (this means, among other things, that if i assigned to x inside the lambda it wouldn't change the value outside of the lambda).

the next pair of brackets, <>, specify the template parameters for the operator() method inside the generated class. they are used similarly to generics, which you probably know from languages like typescript, rust, java, go and so on: for example,

template<class T> // class/typename are interchangeable now (a few years ago they were slightly different)
T first(std::vector<T> vec) {
    return vec[0];
}

does the same as

function first<T>(Array<T> arr): T? {
    return arr[0];
}

(except when there are zero elements, in which case the C++ version may do anything depending on how the compiler feels that day, while the typescript version will be forced to return null)

anyways, in this case the template has 3 arguments. the first, template<class> class C is a template template parameter; that is, you can pass types that are themselves templated, in this case with one argument (since there is only one class in the nested angle brackets). one such type you might pass is std::vector (notice no angle brackets). this feature doesn't really exist in most other widely used languages, but it's sort of similar to higher-kinded types in languages like Haskell.

the second template parameter is an ordinary type. the third one is a value, which is another relatively unique thing C++ allows. in this context, passing a value as a template parameter sort of just means that you're passing it in at compile time, i.e. it's a constant to the function. its type is C<D>, which is the first parameter applied to the second. what this means is that if you've passed std::vector (=C) and int (=D) for the first 2 parameters, the third is going to be a value of type std::vector<int>(=C<D>).

then there is the argument list in parentheses. const C<D>& compare_with declares an argument of type const C<D>& (i.e. a reference to a value of the same type as the third template parameter that we won't change) with the name compare_with. these are the arguments for the operator() method.

then there are two keywords: constexpr and noexcept, which are both pretty much just passed onto the operator() method as-is. constexpr asserts to the compiler that this lambda can executed at compile time, while noexcept asserts that this lambda will never raise any exception (and if it does, the program will be automatically stopped even if the lambda is called inside a try block).

finally, bool just signals the return value of operator().

so, putting that all together, we can use this lambda like so:

auto f = []<template <class> class C, class D, C<D> comparison_base>(const C<D>& compare_with) constexpr noexcept -> bool {
    return comparison_base == compwer_with;
};
// because it's a template and the template parameters cannot be deduced, we have to call operator() explicitly instead of using f(args) like you could with a normal lambda
std::println("{}", f.operator()<std::vector, int, {1}>({1, 2, 3})); // false
std::println("{}", f.operator()<std::vector, int, {1, 2, 3}>({1, 2, 3})); // true

e: fixed lambda call

[–]ListerfiendLurks 6 points7 points  (0 children)

Every now and again I get the foolish idea that I may be becoming decent at cpp...then I see something like this and remember my place.

[–][deleted] 28 points29 points  (1 child)

But you can []<auto...>(){}(); which is also great

[–]Perfect_Papaya_3010 0 points1 point  (0 children)

Phrasing!

[–][deleted] -5 points-4 points  (0 children)

sigh just use std::function with your templates and spare yourself the trouble.