Strange usage of static_cast - c++

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]

Related

Why sizeof can accept a function which has no defination? [duplicate]

One example that frequently comes to mind is :
sizeof expression, where it doesn't evaluates the expression but determines the size by static type. For example :
int func();
sizeof(func());
This is my limit of thinking, so if there are other unevaluated contexts, then what are they?
Fortunately, the standard has a handy list of those (§ 5 [expr] ¶ 8):
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.
Let's look at these in detail.
I will use the following declarations in my examples. The declared functions are never defined anywhere so if a call to them appears in an evaluated context, the program is ill-formed and we will get a link-time error. Calling them in an unevaluated context is fine, however.
int foo(); // never defined anywhere
struct widget
{
virtual ~widget();
static widget& get_instance(); // never defined anywhere
};
typeid
§ 5.2.8 [expr.typeid] ¶ 3:
When typeid is applied to an expression other than a glvalue of a polymorphic class type, the result refers to a std::type_info object representing the static type of the expression. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions are not applied to the expression. If the type of the expression is a class type, the class shall be completely-defined. The expression is an unevaluated operand
(Clause 5).
Note the emphasized exception for polymorphic classes (a class with at least one virtual member).
Therefore, this is okay
typeid( foo() )
and yields a std::type_info object for int while this
typeid( widget::get_instance() )
is not and will probably produce a link-time error. It has to evaluate the operand because the dynamic type is determined by looking up the vptr at run-time.
<rant>I find it quite confusing that the fact whether or not the static type of the operand is polymorphic changes the semantics of the operator in such dramatic, yet subtle, ways.</rant>
sizeof
§ 5.3.3 [expr.sizeof] ¶ 1:
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 parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, to the parenthesized name of such types, or to a glvalue that designates a bit-field.
The following
sizeof( foo() )
is perfectly fine and equivalent to sizeof(int).
sizeof( widget::get_instance() )
is allowed too. Note, however, that it is equivalent to sizeof(widget) and therefore probably not very useful on a polymorphic return type.
noexcept
§ 5.3.7 [expr.unary.noexcept] ¶ 1:
The noexcept operator determines whether the evaluation of its operand, which is an unevaluated operand (Clause 5), can throw an exception (15.1).
The expression
noexcept( foo() )
is valid and evaluates to false.
Here is a more realistic example that is also valid.
void bar() noexcept(noexcept( widget::get_instance() ));
Note that only the inner noexcept is the operator while the outer is the specifier.
decltype
§ 7.1.6.2 [dcl.type.simple] ¶ 4.4:
The operand of the decltype specifier is an unevaluated operand (Clause 5).
The statement
decltype( foo() ) n = 42;
declares a variable n of type int and initializes it with the value 42.
auto baz() -> decltype( widget::get_instance() );
declares a function baz that takes no arguments and returns a widget&.
And that's all there are (as of C++14).
The standard term is an unevaluated operand and you can find it in [expr]
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 ]
5.2.8 covers typeid
5.3.3 covers sizeof
5.3.7 covers noexcept
7.1.6.2 covers simple type specifiers such as auto and decltype and POD types like int, char, double etc.

When we can declare, not define, but still use these function in C++ [duplicate]

One example that frequently comes to mind is :
sizeof expression, where it doesn't evaluates the expression but determines the size by static type. For example :
int func();
sizeof(func());
This is my limit of thinking, so if there are other unevaluated contexts, then what are they?
Fortunately, the standard has a handy list of those (§ 5 [expr] ¶ 8):
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.
Let's look at these in detail.
I will use the following declarations in my examples. The declared functions are never defined anywhere so if a call to them appears in an evaluated context, the program is ill-formed and we will get a link-time error. Calling them in an unevaluated context is fine, however.
int foo(); // never defined anywhere
struct widget
{
virtual ~widget();
static widget& get_instance(); // never defined anywhere
};
typeid
§ 5.2.8 [expr.typeid] ¶ 3:
When typeid is applied to an expression other than a glvalue of a polymorphic class type, the result refers to a std::type_info object representing the static type of the expression. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions are not applied to the expression. If the type of the expression is a class type, the class shall be completely-defined. The expression is an unevaluated operand
(Clause 5).
Note the emphasized exception for polymorphic classes (a class with at least one virtual member).
Therefore, this is okay
typeid( foo() )
and yields a std::type_info object for int while this
typeid( widget::get_instance() )
is not and will probably produce a link-time error. It has to evaluate the operand because the dynamic type is determined by looking up the vptr at run-time.
<rant>I find it quite confusing that the fact whether or not the static type of the operand is polymorphic changes the semantics of the operator in such dramatic, yet subtle, ways.</rant>
sizeof
§ 5.3.3 [expr.sizeof] ¶ 1:
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 parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, to the parenthesized name of such types, or to a glvalue that designates a bit-field.
The following
sizeof( foo() )
is perfectly fine and equivalent to sizeof(int).
sizeof( widget::get_instance() )
is allowed too. Note, however, that it is equivalent to sizeof(widget) and therefore probably not very useful on a polymorphic return type.
noexcept
§ 5.3.7 [expr.unary.noexcept] ¶ 1:
The noexcept operator determines whether the evaluation of its operand, which is an unevaluated operand (Clause 5), can throw an exception (15.1).
The expression
noexcept( foo() )
is valid and evaluates to false.
Here is a more realistic example that is also valid.
void bar() noexcept(noexcept( widget::get_instance() ));
Note that only the inner noexcept is the operator while the outer is the specifier.
decltype
§ 7.1.6.2 [dcl.type.simple] ¶ 4.4:
The operand of the decltype specifier is an unevaluated operand (Clause 5).
The statement
decltype( foo() ) n = 42;
declares a variable n of type int and initializes it with the value 42.
auto baz() -> decltype( widget::get_instance() );
declares a function baz that takes no arguments and returns a widget&.
And that's all there are (as of C++14).
The standard term is an unevaluated operand and you can find it in [expr]
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 ]
5.2.8 covers typeid
5.3.3 covers sizeof
5.3.7 covers noexcept
7.1.6.2 covers simple type specifiers such as auto and decltype and POD types like int, char, double etc.

Which paragraph in the C++ Standard validates the expression `sizeof(S::m + 42)`used in the example below?

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.

How is this size computed?

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.

What are unevaluated contexts in C++?

One example that frequently comes to mind is :
sizeof expression, where it doesn't evaluates the expression but determines the size by static type. For example :
int func();
sizeof(func());
This is my limit of thinking, so if there are other unevaluated contexts, then what are they?
Fortunately, the standard has a handy list of those (§ 5 [expr] ¶ 8):
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.
Let's look at these in detail.
I will use the following declarations in my examples. The declared functions are never defined anywhere so if a call to them appears in an evaluated context, the program is ill-formed and we will get a link-time error. Calling them in an unevaluated context is fine, however.
int foo(); // never defined anywhere
struct widget
{
virtual ~widget();
static widget& get_instance(); // never defined anywhere
};
typeid
§ 5.2.8 [expr.typeid] ¶ 3:
When typeid is applied to an expression other than a glvalue of a polymorphic class type, the result refers to a std::type_info object representing the static type of the expression. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions are not applied to the expression. If the type of the expression is a class type, the class shall be completely-defined. The expression is an unevaluated operand
(Clause 5).
Note the emphasized exception for polymorphic classes (a class with at least one virtual member).
Therefore, this is okay
typeid( foo() )
and yields a std::type_info object for int while this
typeid( widget::get_instance() )
is not and will probably produce a link-time error. It has to evaluate the operand because the dynamic type is determined by looking up the vptr at run-time.
<rant>I find it quite confusing that the fact whether or not the static type of the operand is polymorphic changes the semantics of the operator in such dramatic, yet subtle, ways.</rant>
sizeof
§ 5.3.3 [expr.sizeof] ¶ 1:
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 parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, to the parenthesized name of such types, or to a glvalue that designates a bit-field.
The following
sizeof( foo() )
is perfectly fine and equivalent to sizeof(int).
sizeof( widget::get_instance() )
is allowed too. Note, however, that it is equivalent to sizeof(widget) and therefore probably not very useful on a polymorphic return type.
noexcept
§ 5.3.7 [expr.unary.noexcept] ¶ 1:
The noexcept operator determines whether the evaluation of its operand, which is an unevaluated operand (Clause 5), can throw an exception (15.1).
The expression
noexcept( foo() )
is valid and evaluates to false.
Here is a more realistic example that is also valid.
void bar() noexcept(noexcept( widget::get_instance() ));
Note that only the inner noexcept is the operator while the outer is the specifier.
decltype
§ 7.1.6.2 [dcl.type.simple] ¶ 4.4:
The operand of the decltype specifier is an unevaluated operand (Clause 5).
The statement
decltype( foo() ) n = 42;
declares a variable n of type int and initializes it with the value 42.
auto baz() -> decltype( widget::get_instance() );
declares a function baz that takes no arguments and returns a widget&.
And that's all there are (as of C++14).
The standard term is an unevaluated operand and you can find it in [expr]
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 ]
5.2.8 covers typeid
5.3.3 covers sizeof
5.3.7 covers noexcept
7.1.6.2 covers simple type specifiers such as auto and decltype and POD types like int, char, double etc.