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.
Related
Starting from C++20 closure types without captures have default constructor, see https://en.cppreference.com/w/cpp/language/lambda:
If no captures are specified, the closure type has a defaulted default constructor.
But what about closure types that capture, how can their objects be constructed?
One way is by using std::bit_cast (provided that the closure type can be trivially copyable). And Visual Studio compiler provides a constructor for closure type as the example shows:
#include <bit>
int main() {
int x = 0;
using A = decltype([x](){ return x; });
// ok everywhere
constexpr A a = std::bit_cast<A>(1);
static_assert( a() == 1 );
// ok in MSVC
constexpr A b(1);
static_assert( b() == 1 );
}
Demo: https://gcc.godbolt.org/z/dnPjWdYx1
Considering that both Clang and GCC reject A b(1), the standard does not require the presence of this constructor. But can a compiler provide such constructor as an extension?
But what about closure types that capture, how can their objects be constructed?
You can't. They can only be created from the lambda expression.
And no, bit_cast does not "work everywhere". There is no rule in the C++ standard which requires that any particular lambda type must be trivially copyable (or the same size as its capture member for that matter). The fact that no current implementations break your code does not mean that future implementations cannot.
And it definitely won't work if you have more than one capture member.
Just stop treating lambdas like a cheap way to create a type. If you want to make a callable type with members that you can construct, do that:
#include <bit>
int main() {
struct A
{
int x = 0;
constexpr auto operator() {return x;}
};
// ok everywhere
constexpr A b(1);
static_assert( b() == 1 );
}
Since this is tagged language-lawyer, here's what the C++ standard has to say about all this.
But what about closure types that capture, how can their objects be constructed?
The actual part of the standard that cppreference link is referencing is [expr.prim.lambda.general] - 7.5.5.1.14:
The closure type associated with a lambda-expression has no default constructor if the lambda-expression has a lambda-capture and a defaulted default constructor otherwise. It has a defaulted copy constructor and a defaulted move constructor ([class.copy.ctor]). It has a deleted copy assignment operator if the lambda-expression has a lambda-capture and defaulted copy and move assignment operators otherwise ([class.copy.assign]).
However, clauses 1 and 2 say:
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 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:
[unrelated stuff]
Which means that (apart from the unrelated exceptions), the described interface of the lambda as stated is exhaustive. Since no other constructors than the default one is listed, then that's the only one that is supposed to be there.
N.B. : A lambda may be equivalent to a class-based functor, but it is not purely syntactical sugar. The compiler/implementation does not need a constructor in order to construct and parametrize the lambda's type. It's just programmers who are prevented from creating instances by the lack of constructors.
As far as extensions go:
But can a compiler provide such constructor as an extension?
Yes. A compiler is allowed to provide this feature as an extension as long as all it does is make programs that would be ill-formed functional.
From [intro.compliance.general] - 4.1.1.8:
A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this document. Having done so, however, they can compile and execute such programs.
However, for the feature at hand, MSVC would be having issues in its implementation as an extension:
It should be emmiting a diagnostic.
By its own documentation, it should refuse the code when using /permissive-. Yet it does not.
So it looks like MSVC is, either intentionally or not, behaving as if this was part of the language, which is not the case as far as I can tell.
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.
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.
Studying about "noexcept specifier(and operator)", I wrote a simple code. And I am surprised that this piece of code:
void asdf() noexcept {}
int main()
{
auto f = asdf;
std::cout << std::boolalpha << noexcept(f()) << std::endl;
}
prints false, even function "asdf" is noexcept-specified.
So while searching why this mysterious phenomenon is happening, I found C++17's "exception specifier type system"- P0012R1.
According to this (accepted) proposal, since C++17; as noexcept is part of function type, will the code above print true?
And one more, in this question's one line:
std::function<void() noexcept> f
The noexcept specifying seems ignored in C++14 or 11.
Will this code work as intended in C++17?
According to this (accepted) proposal, since C++17; as noexcept is part of function type, will the code above print true?
Yes.
The type of f will be deduced to void(*)() noexcept since the function-to-pointer conversion applied to asdf will preserve the noexcept property. A call to a noexcept function pointer certainly cannot throw an exception, unless one of its subexpressions does.
For the exact wording, see [expr.unary.noexcept]/3 and [expect.spec]/13. Note that the new wording in the latter paragraph from the C++17 draft comes from P0012R1, which is linked in the OP.
The result of the noexcept operator is true if the set of potential exceptions of the expression ([except.spec]) is empty, and false otherwise.
...
If e is a function call ([expr.call]):
If its postfix-expression is a (possibly parenthesized) id-expression ([expr.prim.id]), class member access ([expr.ref]), or pointer-to-member operation ([expr.mptr.oper]) whose cast-expression is an id-expression, S is the set of types in the exception specification of the entity selected by the contained id-expression (after overload resolution, if applicable).
...
So the set of potential exceptions of f() is the same as the set of types in the exception specification of f, which is empty since f is declared noexcept.
Let's move on to the second question:
The noexcept specifying seems ignored in C++14 or 11. Will this code work as intended in C++17?
Your question seems to be: will std::function<void() noexcept> refuse to hold a function that can throw exceptions?
I would say that it's unclear. In the present wording of the standard, std::function<void() noexcept> is not actually defined, just as std::function<double(float) const> is not defined. This was of course not an issue in C++14, since noexcept was not considered part of a function's type.
Will std::function<void() noexcept> simply break in C++17? That's uncertain to me. Let's take a look at the current wording to guess what the behaviour "should" be.
The standard requires the argument to the constructor of std::function<R(ArgTypes..)> to be "Lvalue-Callable" for argument types ArgTypes... and return type R, which means:
A callable type ([func.def]) F is Lvalue-Callable for argument types ArgTypes and return type R if the expression INVOKE(declval<F&>(), declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause [expr]), is well formed ([func.require]).
Perhaps there should be an additional requirement that if the function type is noexcept, then noexcept(INVOKE(...)) must be true as well. Nonetheless, this wording is not present in the current draft.
In P0012R1, there is a comment that:
It is an open issue how to propagate "noexcept" through std::function.
My guess is that they mean that it is not clear how std::function could be implemented if this additional requirement were imposed. Hopefully, someone else can provide more details.
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).