Consider this piece of code:
class shy {
private:
int dont_touch; // Private member
public:
static const shy object;
};
const shy shy::object = []{
shy obj;
obj.dont_touch = 42; // Accessing a private member; compiles; WHY?
return obj;
}();
int main()
{
}
Live code (Clang)
Live code (GCC)
It seems really unintuitive to me. What does the C++11/14 standard say about this? Is this a GCC/Clang bug?
As already answered in the comments #Tony D and #dyp:
§ 9.4.2/2 Static data members [class.static.data]:
The initializer expression in the
definition of a static data member is in the scope of its class.
The above means that static data members and their initializers can access other private and protected members of their class.
Also consdering the standard § 5.1.2/2&3 Lambda expressions [expr.prim.lambda]:
2 The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5). [ Note: A closure object behaves like a function object (20.9).-end note]
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.
Combining the above we end up to the conclusion that the prvalue temporary closure object of your lambda is declared and defined in the initializer of the static const data member shy::object and consequently the scope of the lambda's closure object is the scope of class shy. As such it can access private members of class shy.
Related
Given the following code:
struct f {
};
int main(){
constexpr f f1 ;
//const f f1 ; // This also has the same issue
//constexpr f f1 = {} ; //This works
}
clang and gcc disagree over whether it is valid, with clang providing the following diagnostic (see it live):
error: default initialization of an object of const type 'const f' without a user-provided default constructor
constexpr f f1 ;
^
{}
As far as I can tell f is a literal type and it is initialized by the implicitly defaulted constructor which should allow it to be declared constexpr. Who is correct here?
Note, clang does accept the declaration of f1 if I explicitly add a constexpr default constructor:
constexpr f() {} ;
Does the answer change is f is not an aggregate?
If we start from the draft C++14 standard section 7.1.5 [dcl.constexpr] we can find the requirements for a constexpr object declaration are:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19).
So is f is a literal type?
Section 3.9 [basic.types] says:
A type is a literal type if it is:
and covers classes in the following bullet:
a class type (Clause 9) that has all of the following properties
it has a trivial destructor,
it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template
that is not a copy or move constructor, and
all of its non-static data members and base classes are of non-volatile literal types.
So we are okay on the first and third bullet. To cover the second bullet, we could note that f is an aggregate but if we modify the example slightly, for example if f looked like this:
struct f {
private:
int x = 0 ;
} ;
which would not be an aggregate in either C++11 or C++14 but the issue would still exist. Then we need to show it has a constexpr constructor. Does f have a constexpr constructor?
As far as I can tell section 12.1 [class.ctor] says yes:
[...] If that user-written default constructor would satisfy the requirements of a constexpr constructor (7.1.5),
the implicitly-defined default constructor is constexpr. [...]
But we are unfortunately required to have a user-provided constructor by section 8.5 which says:
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type
with a user-provided default constructor.
So it looks like clang is correct here and if we look at the following clang bug report: "error: default initialization of an object of const type 'const Z' requires a user-provided default constructor" even when no constructor needed. So clang is basing their lack of support for this due to defect report 253 which does not currently have a proposed wording and it says (emphasis mine):
Paragraph 9 of 8.5 [dcl.init] says:
If no initializer is specified for an object, and the object is of
(possibly cv-qualified) non-POD class type (or array thereof), the
object shall be default-initialized; if the object is of
const-qualified type, the underlying class type shall have a
user-declared default constructor. Otherwise, if no initializer is
specified for an object, the object and its subobjects, if any, have
an indeterminate initial value; if the object or any of its subobjects
are of const-qualified type, the program is ill-formed.
What if a const POD object has no non-static data members? This wording requires an empty initializer for such cases
[...]
Similar comments apply to a non-POD const object, all of whose non-static data members and base class subobjects have default constructors. Why should the class of such an object be required to have a user-declared default constructor?
The defect report is still open but the last comment on it says:
If the implicit default constructor initializes all subobjects, no initializer should be required.
and also notes:
This issue should be brought up again in light of constexpr constructors and non-static data member initializers.
Note the constraints on const qualified types moved around in section 8.5 since the defect report came about. This was due to proposal N2762: Not so Trivial Issues with Trivial which was pre C++11.
Although the defect report is still open, given the constexpr changes and non-static data member initializers it does not seem like a necessary restriction anymore. Especially considering the following requirement on a constexpr constructor:
every non-variant non-static data member and base class sub-object shall be initialized (12.6.2);
My question is about lambda scope for the static member initializers. Consider the following test:
#include <functional>
#include <iostream>
struct S {
static const std::function<void(void)> s_func;
};
const std::function<void(void)> S::s_func = []() {
std::cout << "Hello from " << __PRETTY_FUNCTION__ << std::endl;
};
int main(void) {
S::s_func();
return 0;
}
gcc starting from 4.8 defines the lambda in the scope of S, so the program outputs something like this:
Hello from S::<lambda()>
(gcc-4.8.2 has a different definition for __FUNCTION__ & Co macros, but nevertheless the lambda is still defined within S)
Meanwhile gcc-4.7 defines the lambda in the global scope, so the program outputs
Hello from <lambda()>
Probably newer gcc are more standard-compliant. However I'd like to ask if the standard actually specifies this aspect or it can be implementation-dependent.
Update: as #user5434961 suggested that all __FUNCTION__-alike macros are implementation dependent, so it's better to avoid them in a standard-compliant test. So here is the example which can be compiled if a compiler defines such lambdas within S scope and breaks the compilation otherwise:
#include <functional>
#include <iostream>
struct S {
static const std::function<void(void)> s_func;
private:
static const int s_field;
};
const std::function<void(void)> S::s_func = []() {
std::cout << "Hello from S::s_func. S::s_field = " << S::s_field << std::endl;
};
const int S::s_field = 1;
int main(void) {
S::s_func();
return 0;
}
This issue has been raised before but I cannot find the relevant bug report. Here's a broken link for a MSVC bug report that was supposedly filed (it's still not fixed in 2015: you can test it at rise4fun). It has however been fixed somewhere between 4.7 and 4.8 for GCC. The relevant standardese used to back this up as a bug is:
[C++11, 9.4.2/2] The initializer expression in the definition of a
static data member is in the scope of its class.
[C++11, 5.1.2/2] The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5).
[C++11, 5.1.2/3] The type of the 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. 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.
Previously
Why lambda in static initializer can't access private members of class in VC++2013?
C++11 lambdas can access my private members. Why?
Why is it not possible to use private method in a lambda?
I guess it should be in the class scope. Quoted from cppreference (emphasis mine):
The lambda expression constructs an unnamed prvalue temporary object
of unique unnamed non-union non-aggregate type, known as closure type,
which is declared (for the purposes of ADL) in the smallest block
scope, class scope, or namespace scope that contains the lambda
expression.
In the out-of-line definition of S::s_func, you enter into the scope of S the time S:: is encountered. So, the lambda expression is contained in the class scope of S. As the closer type is a member of S, access to private members of S is granted.
This question already has answers here:
C++11 lambda implementation and memory model
(2 answers)
Closed 7 years ago.
Where do lambdas and std::functions store variables captured by value?
int i = 1;
auto l = [i](void) mutable { return i++; };
std::function<int(void)> f = l;
Do they call the new operator? If I provide my own new operator, will it be used by lambdas?
From [expr.prim.lambda] 5.1.2(15)
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [ Note: If the
captured entity is a reference to a function, the corresponding data member is also a reference to a function. —end note ] A member of an anonymous union shall not be captured by copy.
emphasis mine
So a variable captured by value will be stored as member of the closure type. The closure type is unspecified as per 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 neither an aggregate (8.5.1) nor a literal type (3.9). The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression.
At compile time lambdas are actually turned into classes. So if you had the following.
int a; Widget widget;
auto somelambda = [=a,&widget](){
}
You could actually think of it as
class SomeLambda
{
int a;
Widge& w;
...
}
Which should make you really cautious because you can have dangling references.
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)