Reputation: 2818
I've come across this syntactic construct a few times, and I'm wondering:
It tends to look something like this:
struct SubType : public SomeSuperType {
SubType(int somthing) : SuperType(something), m_foo(*((FooType *)0))
{}
private:
FooType m_foo;
}
To be clear, the code works. But what's the purpose? What would be the status of m_foo
without that line?
Upvotes: 2
Views: 199
Reputation: 241861
I don't think the example is necessarily UB. It depends on the definition of FooType
. Suppose Foo
is an empty class with a constructor that does something:
class Foo {
public:
Foo() { std::cout << "Hey, world! A new Foo just arrived.\n"; }
// I think the default copy and assign constructors do nothing
// with an empty type, but just in case:
Foo(const Foo&) {}
Foo& operator=(const Foo&) { return *this; }
};
Now, suppose I need a Foo, for whatever reason, and I don't want to trigger the constructor. Doing this will not cause any actual dereferencing because operator* does not dereference and the copy constructor doesn't use its reference argument:
Foo(*static_cast<Foo*>(0));
Upvotes: 1
Reputation: 320631
The purpose of this construct is to emulate a fake unnamed object of type SomeType
in situations when you formally need an object, but don't want or can't declare a real one. It has its valid uses and does not necessarily cause undefined behavior.
A classic example would be determining the size of some class member
sizeof (*(SomeClass *) 0).some_member
or a similar application of decltype
decltype((*(SomeClass *) 0).some_member)
Neither of the above examples causes any undefined behavior. In non-evaluated context expressions like *(SomeClass *) 0
are perfectly legal and valid.
You can also see this technique used for illustrative purposes in the language standard itself, as in 8.3.5/12
A trailing-return-type is most useful for a type that would be more complicated to specify before the declarator-id:
template <class T, class U> auto add(T t, U u) -> decltype(t + u);
rather than
template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);
Observe how the (*(T*)0) + (*(U*)0)
expression is used under decltype
to perform compile-time prediction of the result type of binary +
operator between types T
and U
.
Of course, again, such tricks are only valid when used in non-evaluated contexts, as shown above.
Sometimes it is used as an initializer for "null references" as in
SomeType &r = *(SomeType *) 0;
but this actually crosses the boundary of what's legal and produces undefined behavior.
What you have in your specific example is invalid, since it attempts to access an invalid "null lvalue" in evaluated context.
P.S. In C language there's also that peculiar part of specification that says that operators &
and *
cancel each other, meaning that &*(SomeType *) 0
is valid and guaranteed to evaluate to null pointer. But it does not extend to C++.
Upvotes: 6
Reputation: 146968
What does this do? Undefined behaviour.
What might the design reasoning be? A desire to cause undefined behaviour. There's no other rationale.
Upvotes: 5