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)?

1

There are 1 answers

1
YSC On BEST ANSWER

To answer rigorously to your question

I can't find any point in the C++ std that would say any of that invalid/ill-formed code. Can anyone help me?

This is [basic.def.odr]/12.2

There can be more than one definition of a class type [...] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and
  • in each definition of D, corresponding names, looked up according to [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution and after matching of partial template specialization ([temp.over]), except that a name can refer to

    • [ ... nothing of relevance]

In your program, FoobarUser is defined in both of your translation units, but the name Foobar within refers to --- according to the rule of unqualified lookup --- two different entities (moo::test::FooBar and moo:FooBar). And this violates the One-Definition Rule. No diagnostic required.