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.2In your program,
FoobarUseris defined in both of your translation units, but the nameFoobarwithin refers to --- according to the rule of unqualified lookup --- two different entities (moo::test::FooBarandmoo:FooBar). And this violates the One-Definition Rule. No diagnostic required.