Can the 'type' of a lambda expression be expressed?
Asked Answered
C

5

55

Thinking of lambda expressions as 'syntactic sugar' for callable objects, can the unnamed underlying type be expressed?

An example:

struct gt {
    bool operator() (int l, int r) {
        return l > r;
    }
} ;

Now, [](int l, int r) { return l > r; } is an elegant replacement for the above code (plus the necessary creation of callable objects of gt), but is there a way to express gt (the type) itself?

A simple usage:

std::set<int, gt> s1;  // A reversed-order std::set
// Is there a way to do the same using a lambda?
std::set<int, some-magic-here-maybe([](int l, int r) { return l > r; }) > s2;
Clef answered 5/10, 2010 at 20:4 Comment(5)
this is one reason i didn't like lambda expression. suppose you need same comparator in 20 areas of your code. would you write the comparator 20 times or write a single time and pass as functor/function pointer?Ionogen
@Donotalo: so you're saying that because there are cases where it's not the best solution, it can never be a good solution? The reason for lambdas is that often oyu only need that comparator in one place. If you need it 20 times, then I don't think anyone ever suggested expressing it as a lambda.Swashbuckling
@Donotalo: lambdas are usually simple enough to be copy-pasted at different places in your program ; code re-use is good for non-trivial functions, but you shouldn't use a lambda for themRounders
ok got the point. actually i never needed something like lambda in a single place. usually i define operator<() for my classes that needs to be sorted. that's why i didn't understand why lambda is required.Ionogen
@Donotalo: Not only that, but you can actually bind a lambda expression to a variable if you want through the use of std::function with the appropriate set of parameters.Yonder
U
62

No, you cannot put it into decltype because

A lambda-expression shall not appear in an unevaluated operand

You can do the following though

auto n = [](int l, int r) { return l > r; };
std::set<int, decltype(n)> s(n);

But that is really ugly. Note that each lambda expression creates a new unique type. If afterwards you do the following somewhere else, t has a different type than s

auto m = [](int l, int r) { return l > r; };
std::set<int, decltype(m)> t(m);

You can use std::function here, but note that this will incur a tiny bit of runtime cost because it needs an indirect call to the lambda function object call operator. It's probably negligible here, but may be significant if you want to pass function objects this way to std::sort for example.

std::set<int, function<bool(int, int)>> s([](int l, int r) { return l > r; });

As always, first code then profile :)

Untrue answered 5/10, 2010 at 20:11 Comment(8)
I thought of using auto and declytype in combination that way when I read the question. chuckle Using that combination to extract the type of an expression that isn't really meant to have a visible type is kind of amusing.Steppe
The "A lambda-expression shall not appear in an unevaluated operand" seems silly, not well thought out. What's it FOR, do you know?Counterpressure
@Cheersandhth.-Alf to prevent the need of mangling it in a template parameter, so that it would need to be part of the signature of a function template (so the ABI would need to define mangling not only for expressions but for arbitrary statements). Since each expr is an object of unique type, I'm unsure of the use of putting it into sizeof or decltype anyway. That's how I understand it, but I could be wrong, I'm not an expert on that. RichardSmith of the clang folks would know.Untrue
@JohannesSchaub-litb: Thanks for the thinking. I think it's trivial to generate systematic class names for the lambdas, just numbering them in each translation unit. But given the type, there's no way to instantiate a lambda that has captures. On the third hand, the type is available when a lambda is passed to a function template (argument type deduction), so if it's about safety then the type needs to be safe already. Not sure about use case... ;-)Counterpressure
@Cheersandhth.-Alf hm, are you saying something like that in a TU []{}; the type of the lambda has a 1 somewhere and in []{};[]{}; the second lambas's type has a 2 somewhere? I don't think that solves the problem. It's allowed to redeclare templates in TUs that look different, as long as the template itself looks and behaves the same. If one TU has more lambdas before the template than another, the templates would be mangled differently if they would use the lambdas' type to mangle the lambda expression. Not a good idea.Untrue
I think it was about cases like template<typename T> illformed_test<decltype([]{ T t; t.f(); }), void> f(); which a developer could write to require T to be default constructible and have a function f.Untrue
@JohannesSchaub-litb: Every translation unit already has a unique id, for anonymous namespace, so uniqueness isn't a problem. But there is the use case thing. I just find this restriction odd, apparently without good reason.Counterpressure
C++20 now allows lambdas in unevaluated operand :)Commode
P
1

Direct answer to your question: No.

You'll need to use something that is assignable from any type resembling a functor that has a well defined type. One example is std::function as shown in sbi's answer. That isn't, however, the type of the lambda expression.

Purree answered 5/10, 2010 at 20:11 Comment(0)
E
1

Since C++ 20, lambdas without captures are default constructible, so one can simply pass decltype of the lambda inline as the second template parameter to std::set.

std::set<int, decltype([](int l, int r){return l > r;})> s2;

Of course, in this specific case of reversing the natural order, std::greater is more appropriate.

std::set<int, std::greater<>> s2;
Elegist answered 2/2, 2024 at 14:33 Comment(0)
W
0

You could use a small class lambda_wrapper<>, to wrap a lambda at low costs. Its much more faster than std::function because there are no virtual function call and a dynamic memory alloc. Wrapper works by deducing the lambda arguments list and return type.

#include <iostream>
#include <functional>
#include <set>

template <typename T, typename ... Args>
struct lambda_wrapper : public lambda_wrapper<decltype(&T::operator())(Args...)> {};

template <typename L>
struct lambda_wrapper<L> {
private:
    L lambda;

public:
    lambda_wrapper(const L & obj) : lambda(obj) {}

    template<typename... Args>
    typename std::result_of<L(Args...)>::type operator()(Args... a) {
        return this->lambda.operator()(std::forward<Args>(a)...);
    }

    template<typename... Args> typename
    std::result_of<const L(Args...)>::type operator()(Args... a) const {
        return this->lambda.operator()(std::forward<Args>(a)...);
    }
};
template <typename T>
auto make_lambda_wrapper(T&&t) {
    return lambda_wrapper<T>(std::forward<T>(t));
}
int main(int argc, char ** argv) 
{
    auto func = make_lambda_wrapper([](int y, int x) -> bool { return x>y; });
    std::set<int, decltype(func)> ss(func);
    std::cout << func(2, 4) << std::endl;
}
W answered 21/7, 2017 at 20:57 Comment(3)
How is auto func = make_lambda_wrapper([](int y, int x) { return x>y; }); more useful than just auto func = [](int y, int x) { return x>y; };?Spacing
@Spacing "auto func" type is anonymous class, lamda_wrapper is a explicit class type. So you could store lambda_wrappers in a std::map or set for example. Its not possible to store uniformly a collection of raw lambdas because each one would be of different type. But you could define a vector<lambda_wrapper> and store any lambdas with matching signature. Of course you could do it with std::function as well BUT std::function is way more slow (vptr + atomic lock + heap alloc)W
No, you can't store two different lambda_wrappers in a container, only two copies of the same lambda. Which you can also do with an auto lambda directly.Spacing
A
0

In Microsoft Visual Studio at least (I haven't tried this with other compilers), and if you don't capture anything, the type seems to be a regular function pointer:

std::string (*myFunctionPointer)(int x) = [] (int x) {
  char buffer[10];
  return std::string("Test ") + itoa(x, buffer, 10);
};
std::string testOutput = myFunctionPointer(123);
Anticipation answered 26/4, 2018 at 11:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.