Here is the example:
Main.cpp
:
#include "MooFoobar.h"
#include "MooTestFoobar.h"
#include "FoobarUser.h"
namespace moo::test::xxx {
struct X
{
void* operator new(const size_t size);
FoobarUser m_User;
};
void* X::operator new(const size_t size)
{
printf("Allocated size: %zd\n", size);
return malloc(size);
}
} // namespace moo::test::xxx
int main()
{
new moo::test::xxx::X;
printf("Actual size: %zd, member size: %zd\n", sizeof(moo::test::xxx::X), sizeof(moo::test::xxx::FoobarUser));
return 0;
}
MooFoobar.h
:
namespace moo {
struct Foobar
{
char m_Foo[64];
};
} // namespace moo
MooTestFoobar.h
:
namespace moo::test {
struct Foobar
{
char m_Foo[32];
};
} // namespace moo::test
FoobarUser.h
:
#include "MooFoobar.h"
namespace moo::test::xxx {
struct FoobarUser
{
FoobarUser();
~FoobarUser();
Foobar m_Foobar;
};
} // namespace moo::test::xxx
FoobarUser.cpp
:
#include "FoobarUser.h"
#include <cstdio>
moo::test::xxx::FoobarUser::FoobarUser()
: m_Foobar()
{
printf("FoobarUser constructor, size: %zd\n", sizeof(*this));
}
moo::test::xxx::FoobarUser::~FoobarUser()
{}
So what is going on here: depending on order of includes unqualified name is resolved in different types and in FoobarUser.cpp
we get size 64
, in Main.cpp
we get size 32
. Not only sizeof
is different - operator new
is called with incorrect (32
) size, but constructor will initialize size of 64
, those leading to memory corruption.
In both clang and msvc the result of this program is:
Allocated size: 32
FoobarUser constructor, size: 64
Actual size: 32, member size: 32
This sounds very fishy and basically means that unqualified names are no-go if there is name-clash, because depending on include order it may lead to what essentially is incorrect program.
But I can't find any point in the C++ std that would say any of that invalid/ill-formed code. Can anyone help me?
Is it REALLY by standard and not some elaborate mass-compiler issue (though I can't really see how compilers can resolve that situation)?
To answer rigorously to your question
This is
[basic.def.odr]/12.2
In your program,
FoobarUser
is defined in both of your translation units, but the nameFoobar
within refers to --- according to the rule of unqualified lookup --- two different entities (moo::test::FooBar
andmoo:FooBar
). And this violates the One-Definition Rule. No diagnostic required.