3
votes

I have a template child class (the template typename is not related to parent or child, so not a CRTP inheritance case), and the parent has a forward-declared class in the private access label section.

GCC (tested on 4.9.2 and 7.2) compiles, but clang (tested on 5.0.0) will complain. This is the repro case:

class TestClass
{
public:
    // Forward-declare as public to make compile with clang
    // class Thing;
private:
    // GCC compiles fine with forward-declaration as private but clang gives error
    class Thing;
    friend class Thing;
    void NotifyOfThing();
};

class TestClass::Thing
{
public:
    static void NotifyOfThing() {}
};

template <typename Unrelated>
class ThingImpl final : public Unrelated
{
private:
    void handleThing()
    {
        TestClass::Thing::NotifyOfThing();
    }
};

int main() {
    ThingImpl<TestClass> implementation;
}

Clang throws an error:

25 : <source>:25:20: error: 'Thing' is a private member of 'TestClass'
        TestClass::Thing::NotifyOfThing();
                   ^
8 : <source>:8:11: note: declared private here
    class Thing;
          ^
1 error generated.
Compiler exited with result code 1

GCC however accepts this.

Now if I remove the template declaration, and make ThingImpl a non-template class, GCC will also have the same complaint about Thing being private.

Can someone explain why this is the case, and which one complies more with the C++ standard? Or does the standard not cover this explicitly?

1
Looks like clang++ will only error if ThingImpl<U>::handleThing() is actually instantiated, but g++ will accept it even if it is instantiated. So to be accurate, your example needs to include code that will cause the member function instantiation. - aschepler
I will add it in: I excluded it to reduce the amount of text but if it makes it clearer I'll add it. - Anton
ThingImpl<TestClass> implementation; causes the instantiation of the class and its implicitly defined default constructor and destructor, but does not cause the instantiation of ThingImpl<TestClass>::handleThing(). clang++ still accepts the posted code. - aschepler
No, clang doesn't: see this repro case on godbolt.org: godbolt.org/g/sfZuCi - Anton
Ah, looks like clang 3.8.0 accepts it, but clang 6.0.0 head rejects it. (So at some point they improved error reporting for non-dependent code in templates which aren't instantiated, which is "ill formed no diagnostic required" (errors optional) according to the Standard.) - aschepler

1 Answers

4
votes

gcc has many bugs when it comes to access checking in templates. See this meta-bug. The situation you have matches 41437 exactly:

class A { struct B { B(); }; };
template<typename T> void f() { A::B b; }
void g() { f<int>(); } // gcc says this is okay

clang is correct, the code is clearly ill-formed. Thing is a private class of TestCalss, and ThingImpl is not a friend of TestClass, so ThingImpl trying to access TestClass::Thing should be an access violation.