I'm searching for a way to identify empty (captureless) lambdas from other lambdas in a template function. I'm currently using C++17 but I'm curious for C++20 answers too.
My code looks like this:
template<typename T>
auto func(T lambda) {
// The aguments of the lambdas are unknown
if constexpr (/* is captureless */) {
// do stuff
}
}
Is it guaranteed by the C++ standard (17 or 20) that a captureless lambda, which is convertible to a function pointer, will also make std::is_empty yield true?
Take this code as an example:
auto a = []{}; // captureless
auto b = [c = 'z']{}; // has captures
static_assert(sizeof(a) == sizeof(b)); // Both are the same size
static_assert(!std::is_empty_v<decltype(b)>); // It has a `c` member
static_assert(std::is_empty_v<decltype(a)>); // Passes. It is guaranteed?
Live example
No, in fact, the standard explicitly grants permission for lambdas to have a size that doesn't line up with their declaration. [expr.prim.lambda.closure]/2 states
The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [ Note: This determines the set of namespaces and classes associated with the closure type ([basic.lookup.argdep]). The parameter types of a lambda-declarator do not affect these associated namespaces and classes. — end note ] The closure type is not an aggregate type. An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:
the size and/or alignment of the closure type,
whether the closure type is trivially copyable ([class.prop]), or
(2.3)
whether the closure type is a standard-layout class ([class.prop]).
An implementation shall not add members of rvalue reference type to the closure type.
emphasis mine
So this allows the implementation to give the lambda a member even if it is capture-less. I don't think any implementation ever would, but they are legally allowed to do so.
Related
A colleague showed me a C++20 program where a closure object is virtually created using std::bit_cast from the value that it captures:
#include <bit>
#include <iostream>
class A {
int v;
public:
A(int u) : v(u) {}
auto getter() const {
if ( v > 0 ) throw 0;
return [this](){ return v; };
}
};
int main() {
A x(42);
auto xgetter = std::bit_cast<decltype(x.getter())>(&x);
std::cout << xgetter();
}
Here main function cannot call x.getter() due to exception. Instead it calls std::bit_cast taking as template argument the closure type decltype(x.getter()) and as ordinary argument the pointer &x being captured for new closure object xgetter. Then xgetter is called to obtain the value of object x, which is otherwise not accessible in main.
The program is accepted by all compilers without any warnings and prints 42, demo: https://gcc.godbolt.org/z/a479689Wa
But is the program well-formed according to the standard and is such 'construction' of lambda objects valid?
But is the program well-formed according to the standard ...
The program has undefined behaviour conditional on leeway given to implementors. Particularly conditional on whether the closure type of the lambda
[this](){ return v; };
is trivially copyable from; as per [expr.prim.lambda.closure]/2:
The closure type is declared in the smallest block scope, class scope,
or namespace scope that contains the corresponding lambda-expression. [...]
The closure type is not an aggregate type. An
implementation may define the closure type differently from what is
described below provided this does not alter the observable behavior
of the program other than by changing:
(2.1) the size and/or alignment of the closure type,
(2.2) whether the closure type is trivially copyable ([class.prop]), or
(2.3) whether the closure type is a standard-layout class ([class.prop]). [...]
This means that whether the constraints of [bit.cast]/1 are fulfilled or not:
template<class To, class From>
constexpr To bit_cast(const From& from) noexcept;
Constraints:
(1.1) sizeof(To) == sizeof(From) is true;
(1.2) is_trivially_copyable_v<To> is true; and
(1.3) is_trivially_copyable_v<From> is true.
is implementation-defined.
... and is such 'construction' of lambda objects valid?
As [expr.prim.lambda.closure]/2.1 also states that the size and alignment of the closure type is implementation-defined, using std::bit_cast to create an instance of the closure type may result in a program with undefined behavior, as per [bit.cast]/2:
Returns: An object of type To. Implicitly creates objects nested within the result ([intro.object]). Each bit of the value representation of the result is equal to the corresponding bit in the object representation of from. Padding bits of the result are unspecified. For the result and each object created within it, if there is no value of the object's type corresponding to the value representation produced, the behavior is undefined. If there are multiple such values, which value is produced is unspecified.
For any kind of practical use, however, I'd argue that if a construct has undefined behavior conditional on implementation leeway details (unless these can be queried with say traits), then the construct should reasonably be considered to have undefined behavior, except possibly for a compiler's internal C++ (e.g. Clang frontend) implementation, where these implementation details are known.
Several times, in code review, I'm told to add contexpr to some named lambda closure declaration, i.e. I'm told to change this
auto lam = [capture list](args){body}
to this:
constexpr auto lam = [capture list](args){body}
Therefore, I'd like to understand what the conditions are that allow a lambda closure to be declared as constexpr, so I can make this change autonomously.
Here I read that
A constexpr variable must satisfy the following requirements:
its type must be a LiteralType.
it must be immediately initialized
the full-expression of its initialization, including all implicit conversions, constructors calls, etc, must be a constant expression
[a C++20-specific condtion]
Where I see that the second condition is verified, but I'd like some help understanding the first and especially the third condition.
Concerning the first condition, at the LiteralType page I understand that it is enough, for a variable to be a LiteralType, to be a possibly cv-qualified class type that has a destructor (how could it not have one?) and is a closure type (which is the case for the lam above), and all non-static data members and base classes are of non-volatile literal types (I'm not sure about this last part in relation to lambdas).
The bottom line is that I'd like to understand how I can understand, by inspection, if I can make a lambda constexpr.
The only thing that decides whether a lambda can be constexpr is the capture list. The body can be non-constexpr and a constexpr lambda can still have parameters like std::string, which are not literal types either.
its type must be a LiteralType.
The LiteralType requirement for lambdas effectively means that all captured members have to be non-volatile literal types too. Defining a lambda is equivalent to defining an anonymous struct where the captured variables are members.
constexpr int x = 0;
constexpr auto lam = [x](){};
// equivalent to:
struct Lam {
int x;
void operator()() {}
};
constexpr Lam lam2{x};
When we think of lambdas as such structs with call operators, it also becomes obvious what all these requirements mean. We can't capture a non-literal type, because that would require storing a non-literal type in this struct. And that can't happen in a constexpr context.
the full-expression of its initialization, including all implicit conversions, constructors calls, etc, must be a constant expression
Once again, this basically states that capturing variables has to happen in a constexpr way. All captured variables must be constexpr, otherwise the initialization won't be. In fact, constexpr variables are all implicitly captured. So what this really means is that a lambda can be made constexpr exactly when it doesn't have a capture list, or that capture list could be omitted.
In the C++ standard, closure types are defined as follows:
[expr.prim.lambda.closure]
The type of a lambda-expression (which is also the type of the closure
object) is a unique, unnamed non-union class type, called the closure
type, whose properties are described below. [...]
The standard does not seem to define whether or not the unnamed non-union class type is final. Would a compiler implementing lambdas as final classes be standard compliant, or do we have the guarantee that one can inherit from lambdas?
The question is not whether it is useful or not to inherit from lambdas: it's a given that it is useful. The question is whether the standard provides this guarantee.
Yes, the closure type must not be final. At least that's my interpretation.
§8.1.5.1 Closure types [expr.prim.lambda.closure]
An implementation may define the closure type differently from what is
described below provided this does not alter the observable behavior
of the program other than by changing:
... [does not apply]
The standard then doesn't describe the closure type as being final. Making it final would alter the observable behavior ergo the closure type must not be final.
Regarding the observable behavior. Consider this:
auto l = []{};
return std::is_final_v<decltype(l)>;
Making the closure type final would clearly modify the observable behavior of a valid program.
As to a use case, it can actually be a very useful feature:
template <class... Fs> struct Overload : Fs ...
{
using Fs::operator()...;
};
template <class... Fs> Overload(Fs...) -> Overload<Fs...>;
auto test()
{
Overload f = {[] (int a) { return a * 100; },
[] (int a, int b) { return a + b;}};
return f(1) + f(2, 3); // 105
}
See it in action on godbolt
Thanks to hvd and rakete1111 for the discussions and feedback in the comments.
The effect of final is specified in [class]/3:
If a class is marked with the class-virt-specifier final and it appears as a class-or-decltype in a base-clause, the program is ill-formed.
That is, it doesn't matter whether the class is final. It only matters whether the class is marked with the final specifier. Since a closure type does not have any declaration in the source file, it is impossible to mark it as final, and thus [class]/3 does not apply.
I can't find anything about throwing exceptions during constructing closure object.
It's oblivious that this expression can throw during copy construction of the vector:
auto v = std::vector<int>(1000000);
[v]{};
But what about empty or "by reference" capture lists like this:
[&]{};
I'm speaking only about construction of closure object now. Calling isn't interesting.
I read 5.1.2 Lambda expressions [expr.prim.lambda], but found nothing special about nothrow guarantee.
According to the standard (draft n3337), §5.1.2/3:
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed nonunion
class type — called the closure type — whose properties are described below. This class type is not
an aggregate (8.5.1). The closure type is declared in the smallest block scope, class scope, or namespace
scope that contains the corresponding lambda-expression.
In a nutshell, a lambda expression ultimately instantiates an anonymous type (known only to the compiler), containing member variables that store the values indicated in the capture list. So imagine you saw a class that looked like this:
class __IgnoreMe__
{
std::vector<int>& _v;
public:
__IgnoreMe__(std::vector<int>& v)
: _v(v)
{
}
void operator()()
{
}
};
(Note: this isn't exactly how the class created by the compiler looks. The standard lays out specific requirements for generated classes, which I've left out for brevity's sake.)
Now imagine you instantiate that class like this:
auto v = std::vector<int>(1000000);
auto my_lambda = __IgnoreMe__(v);
Can the instantiation of my_lambda throw an exception? If so, then the construction of the closure object can throw an exception. (It can't, in this case.)
As for providing nothrow guarantees, the standard doesn't require compilers to account for this, but it doesn't prevent them from doing it either. The end of §5.1.2/3 states:
An implementation may define the closure
type differently from what is described below provided this does not alter the observable behavior of the
program other than by changing:
— the size and/or alignment of the closure type,
— whether the closure type is trivially copyable (Clause 9),
— whether the closure type is a standard-layout class (Clause 9), or
— whether the closure type is a POD class (Clause 9).
This question already has answers here:
What is the type of lambda when deduced with "auto" in C++11?
(8 answers)
Closed 6 years ago.
There is this code:
auto fun = [](int x)->int {return x + 1; };
std::cout << typeid(fun).name() << std::endl;
The result is: Z4mainEUliE_ but c++filt doesn't seem to explain what is it. What is type of lambda expression?
§5.1.2/3 states:
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type
It goes on to say more, but that's the most important bit. A lambda is basically an instance of an anonymous class.
Incidentally, the demangled form of your lambda is main::$_0.
The type of a lambda function is unspecified by the standard (§5.1.2):
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union classtype — called the closure type — whose properties are described below. This class type is not an aggregate (8.5.1). The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression.
It then goes on listing the exact properties a closure type should have.
Therefore there is no general type for a lambda function to have. The compiler will generate a new functor type with unspecified name for each lambda function
What is type of lambda expression?
The type of a lambda expression (the so-called closure) is an unnamed class type with a function call operator automatically generated by the compiler. The internal name the compiler will give it is unspecified.
According to Paragraph 5.1.2/3 of the C++11 Standard:
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed nonunion
class type — called the closure type — whose properties are described below. This class type is not
an aggregate (8.5.1). The closure type is declared in the smallest block scope, class scope, or namespace
scope that contains the corresponding lambda-expression. [...]
Also notice, that the name() member function of the type_info class (the type returned by typeid()) is also implementation-dependent, and the Standard does not require it to be meaningful for a human.
Per Paragraph 18.7.1:
const char* name() const noexcept;
9 Returns: An implementation-defined NTBS.
10 Remarks: The message may be a null-terminated multibyte string (17.5.2.1.4.2), suitable for conversion and display as a wstring (21.3, 22.4.1.4)