5.1.1/2 is stated that:
The keyword this names a pointer to the object for which a
non-static member function (9.3.2) is invoked or a non-static data
member’s initializer (9.2) is evaluated.
And:
Unlike the object expression in other contexts, *this is not
required to be of complete type for purposes of class member access
(5.2.5) outside the member function body.
The following code prints 8:
#include <cstddef>
#include <iostream>
struct Test
{
std::size_t sz = sizeof(this->sz);
};
int main()
{
std::cout << Test{}.sz;
}
5.3.3 says:
The operand is either an expression, which is an unevaluated operand
(Clause 5), or a parenthesized type-id. The sizeof operator shall
not be applied to an expression that has function or incomplete
type...
sizeof this->sz has the same result.
Is this-> considered a no-op in this case and it's essentially equivalent to sizeof(sz)?
Is this-> considered a no-op in this case and it's essentially equivalent to sizeof(sz)?
That's right.
The type of this->sz is std::size_t, a complete type in that context.
The type of *this is not complete here, but you quoted the passage stating why that doesn't matter and we can go straight through to analysing sz specifically.
As such, the this-> had no actual effect on the semantics of the expression, either for better or for worse.
As Sergey said, there is one case where using this-> for member access makes a difference (template bases!), and this is not one of them.
Related
Both gcc and clang accept the following code, and I'm trying to figure out why.
// c++ -std=c++20 -Wall -c test.cc
#include <concepts>
struct X {
int i;
};
// This is clearly required by the language spec:
static_assert(std::same_as<decltype(X::i), int>);
// This seems more arbitrary:
static_assert(std::same_as<decltype((X::i)), int&>);
The first static_assert line makes sense according to [dcl.type.decltype]:
otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, or if E names a set of overloaded functions, the program is ill-formed;
- https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.3
X::i is a valid id-expression in an unevaluated context, so its decltype should be the declared type of i in X.
The second static_assert has me stumped. There's only one clause in [dcl.type.decltype] in which a parenthesized expression yields an lvalue reference: it must be that both compilers consider X::i to be an expression of lvalue category. But I can't find any support for this in the language spec.
Obviously if i were a static data member then X::i would be an lvalue. But for a non-static member, the only hint I can find is some non-normative language in [expr.context]:
In some contexts, unevaluated operands appear ([expr.prim.req], [expr.typeid], [expr.sizeof], [expr.unary.noexcept], [dcl.type.simple], [temp.pre], [temp.concept]).
An unevaluated operand is not evaluated.
[ Note: In an unevaluated operand, a non-static class member may be named ([expr.prim.id]) and naming of objects or functions does not, by itself, require that a definition be provided ([basic.def.odr]).
An unevaluated operand is considered a full-expression.
— end note
]
- https://timsong-cpp.github.io/cppwp/n4861/expr.prop#expr.context-1
This suggests decltype((X::i)) is a valid type, but the definition of full-expression doesn't say anything about value categories. I don't see what justifies int& any more than int (or int&&). I mean an lvalue is a glvalue, and a glvalue is "an expression whose evaluation determines the identity of an object." How can an expression like X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?
Are gcc and clang right to accept this code, and if so what part of the language specification supports it?
remark:
StoryTeller - Unslander Monica's answer makes even more sense in light of the fact that sizeof(X::i) is allowed and that decltype((X::i + 42)) is a prvalue.
How can an expression like X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?
That's not entirely true. That expression can be evaluated (following a suitable transformation in the right context).
[class.mfct.non-static]
3 When an id-expression that is not part of a class member access syntax and not used to form a pointer to member ([expr.unary.op]) is used in a member of class X in a context where this can be used, if name lookup resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression using (*this) as the postfix-expression to the left of the . operator.
So we can have something like
struct X {
int i;
auto foo() const { return X::i; }
};
Where X::i is transformed into (*this).X::i. Which is explicitly allowed on account of
[expr.prim.id]
2 An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
as part of a class member access in which the object expression refers to the member's class or a class derived from that class, or ...
And that meaning of (*this).X::i is always an lvalue denoting the class member i.
So you see, in the contexts where X::i can be evaluated, it always produces an lvalue. So decltype((X::i)) in those context would need to be an lvalue reference type. And while outside of class scope X::i cannot generally be used in an expression, it's still not entirely arbitrary to say its value category is an lvalue. We are just expanding the region of definition a bit (which doesn't contradict the standard, since the standard doesn't define it).
How can an expression like `X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?
Ignoring the misuse of «result», it is [expr.prim.id.qual]/2:
A nested-name-specifier that denotes a class, optionally followed by the keyword template ([temp.names]), and then followed by the name of a member of either that class ([class.mem]) or one of its base classes, is a qualified-id; ... The result is an lvalue if the member is a static member function or a data member and a prvalue otherwise.
The value category of an expression is not determined by the «definition» in [basic.lval], like «an expression whose evaluation determines the identity of an object», but specified for each kind of expression explicitly.
The root of your question seems to be the difference between decltype(X::i) and decltype((X::i)). Why does (X::i) yield a int&? See:
https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.5
otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;
However, the key point here is that the fact that it yields a T& doesn't really matter:
https://timsong-cpp.github.io/cppwp/n4861/expr.type#1
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. [ Note: Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see [basic.life]). — end note ]
What "justifies" it then? Well, when doing decltype(X::i) we're concerned primarily with what the type of X::i is and not its value category or its properties when treated as an expression. However, (X::i) is there if we do care.
This question is on how to use global variable initialization to achieve some side effect before main() is run (e.g., factory class registration before main()). This can be tricky when template is involved. Consider the following piece of code:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
template <bool&>
struct Dummy {};
// Important: Do not delete.
// This line forces the instantiation and initialization of `flag`.
using DummyType = Dummy<flag>;
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is instantiated and.
// A<int>::do_something will be called before main to initialize A<int>::flag.
class B : A<int> {};
If we remove the using, the behavior will be different:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
// The `using` dummy part is missing.
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is not instantiated.
// A<int>::do_something will not be called.
class B : A<int> {};
My question: is the using trick here a compiler specific thing, or is it backed up by the C++ standard? I did some search and found some relevant information about the standard 14.7.1, but still not sure about the rules on using as it's not mentioned in 14.7.1 (my source is this one, not sure if it should be considered as the ground truth). Also, the using Dummy seems somewhat hacky to me. Are there any other workarounds to make it more elegant? The goal is to resolve the instantiation within class A itself so that the side effect related code segments remain private.
Thanks to new wording in C++20, it is possible to give a definitive answer to your question. In the comments, Igor Tandetnik mentioned the relevant paragraph in C++17, namely [temp.inst]/3. In C++20, it is [temp.inst]/4, which has some new relevant wording that I have bolded:
Unless a member of a class template or a member template is a declared specialization, the specialization of
the member is implicitly instantiated when the specialization is referenced in a context that requires the
member definition to exist or if the existence of the definition of the member affects the semantics of the
program; in particular, the initialization (and any associated side effects) of a static data member does not
occur unless the static data member is itself used in a way that requires the definition of the static data
member to exist.
Later in the same section, paragraph 8:
The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression (7.7), even if constant evaluation of the expression is not required or if constant expression evaluation does not use the definition.
In short, if the alias-declaration using DummyType = Dummy<flag>; is instantiated, then the static data member flag is needed for constant evaluation, therefore it is instantiated.
Informally, "needed for constant evaluation" refers to certain situations where a variable or function is not odr-used, but the way it is used in constant evaluation still requires its definition.
Formally, the definition of "needed for constant evaluation" is given in [expr.const]/15:
An expression or conversion is potentially constant evaluated if it is:
a manifestly constant-evaluated expression,
a potentially-evaluated expression (6.3),
an immediate subexpression of a braced-init-list,
an expression of the form & cast-expression that occurs within a templated entity, or
a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.
A function or variable is needed for constant evaluation if it is:
a constexpr function that is named by an expression (6.3) that is potentially constant evaluated, or
a variable whose name appears as a potentially constant evaluated expression that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
"Manifestly constant-evaluated" is defined in p14 of that section:
An expression or conversion is manifestly constant-evaluated if it is: [...] a constant-expression, or [...] [Note 7: A manifestly constant-evaluated expression is evaluated even in an unevaluated operand. — end note]
"constant-expression" is a grammar production that is used in various contexts to denote a conditional-expression that must be a constant expression. We can check the grammar for simple-template-id (which is what Dummy<flag> is):
simple-template-id :
template-name < template-argument-list(opt) >
[...]
template-argument-list :
template-argument ...(opt)
template-argument-list , template-argument ...(opt)
template-argument :
constant-expression
type-id
id-expression
Here, id-expression refers to a template template parameter. In using DummyType = Dummy<flag>;, flag is an argument for a non-type template parameter, so it is a constant-expression.
Since the expression flag is a manifestly constant-evaluated expression, it is a potentially constant-evaluated expression. The variable flag's name "appears as a potentially constant evaluated expression" and it's a variable of reference type, so it is needed for constant evaluation.
Note that the concept of "needed for constant evaluation" was introduced by P0589 to resolve CWG 1581. This was voted as a DR, so its resolution is retroactive. The concepts of "needed for constant evaluation" and "manifestly constant-evaluated" always needed to be in C++; their absence led to issues like the one in this question.
This is the example in [expr.prim.id]/2 (2.3):
struct S {
int m;
};
int i = sizeof(S::m); // OK
int j = sizeof(S::m + 42); // OK
I'd like to know which paragraph in the Standard (N4713) validates the expression sizeof(S::m + 42) used above.
As #M.M points out, the operand of sizeof can be any expression (8.5.2.3.1):
[...] The operand is either an expression, which is an unevaluated operand, or a parenthesized type-id.
There are a few other restrictions noted in 8.5.2.3, but none apply here.
Note that it mentions that it can be an unevaluated operand -- this makes possible to use the non-static class member S::m here, see (8.2.3.1):
An unevaluated operand is not evaluated. [ Note: In an unevaluated operand, a non-static class member may be named ([expr.prim]) and naming of objects or functions does not, by itself, require that a definition be provided ([basic.def.odr]). An unevaluated operand is considered a full-expression. — end note ]
Which mentions the paragraph that you were referring to in the question (8.4.4.2.3):
An id-expression that denotes a non-static data member or non-static member function of a class can only be used: [...] if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
I came over the following code construct in our production environment (heavily simplified however).
#include <iostream>
typedef struct
{
char entry[10];
}
inn_struct;
typedef struct
{
inn_struct directory;
}
out_struct;
struct test
{
static const int
ENTRY_LENGTH = (sizeof((static_cast<out_struct*>(0))->directory.entry)
/ sizeof((static_cast<out_struct*>(0))->directory.entry[0]));
};
int main()
{
test a;
std::cout << test::ENTRY_LENGTH;
}
Now not considering the obviously obfuscated nature of it since it's just the old C way of determining the length of an array ... I am really bothered by the static_cast of the 0 value. ... Is this code acceptable? Can you please attach some passages from the c++ standard to your response which tells me (if) why this code is ok?
Yes, this code is perfectly acceptable. See §5.3.3/1 (emphasis mine).
The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 5), or a paranthesized type-id.
The expression is not evaluated, so there's no problem with what looks like dereferencing a null pointer.
Note also that in C++11, you don't need to jump through that hoop, and can just directly reference the class member with sizeof, thanks to §5/8 (emphasis mine):
In some contexts, unevaluated operands appear (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). An unevaluated operand is not evaluated. An unevaluated operand is considered a full-expression. [ Note: In an unevaluated operand, a non-static class member may be named (5.1) and naming of objects or functions does not, by itself, require that a definition be provided (3.2). — end note ]
and §5.1.1/13:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
..
if that id-expression denotes a non-static data member and it appears in an unevaluated context [Example:
struct S {
int m;
};
int i = sizeof(S::m); // OK
int j = sizeof(S::m + 42); // OK
- end example]
I was trying to validate this statement (my emphasis) in paragraph §5.1.1/8 (page 87) of the C++11 Standard
A nested-name-specifier that denotes a class, optionally followed by
the keyword template (14.2), and then followed by the name of a member
of either that class (9.2) or one of its base classes (Clause 10), is
a qualified-id; 3.4.3.1 describes name lookup for class members that
appear in qualified-ids. The result is the member. The type of the
result is the type of the member. The result is an lvalue if the
member is a static member function or a data member and a prvalue
otherwise.
with the following snippet:
#include <iostream>
namespace N {
class A {
public:
int i;
void f();
};
}
int main()
{
std::cout << &N::A::f << '\n';
std::cout << &N::A::i << '\n';
}
clang and gcc compile this code and VS2013 requires the definition of the member function f.
All three of them print
1
1
but I have no idea where these numbers come from.
live example
According to the paragraph highlighted above the expressions N::A::f is a prvalue, as f is not a static member function. Nonetheless, I was able to take its address in the code.
At the same time, in §5.3.1/3 one reads (emphasis mine):
The result of the unary & operator is a pointer to its operand. The
operand shall be an lvalue or a qualified-id. If the operand is a
qualified-id naming a non-static member m of some class C with type T,
the result has type “pointer to member of class C of type T” and is a
prvalue designating C::m.
which gives the impression that neither N::A::f nor N::A::i are lvalues, as they are qualified-ids.
but I have no idea where these numbers come from.
Pointer-to-members aren't pointers. No operator<< can output their original value, the best and only match is the one that outputs bool values. Thus they are converted to bool (which obviously yields true) and the output is 1. Try to insert std::boolalpha and check the output again.
Nonetheless, I was able to take its address in the code.
How is that a surprise to you? You quoted the part that allows and explains this exact construct. It clearly states that taking the adress of a qualified-id that names a non-static member designates that member.
qualified-ids are not only lvalues or rvalues. It completely depends on context. If they designate non-static members from outside that members class or any subclass of it, they have to be prvalues as they don't designate any specific object but rather a value (or information, in other words -- the type and an offset).