Search code examples
c++templatesaccess-specifier

Using a private inner class as template parameter default value (gcc vs clang)


I use the (simplified) following code to implement pimpl idiom

// in .h
#include <memory>
template<class T, class Impl = typename T::Impl> class Pimpl {
public:
    Pimpl() : m_impl(std::make_unique<Impl>()) {}
    Impl * operator->() { return m_impl.get(); }
    const std::unique_ptr<Impl> m_impl;
};

class MyClass {
public:
    int getData();
private:
    class Impl;
    Pimpl<MyClass> m_impl;
};

// in .cpp
class MyClass::Impl {
public:
    int data = 1;
};

int MyClass::getData() {
    return m_impl->data;
}

int main() {
    MyClass c;
    return c.getData();
}

It works fine with g++, but building with clang++ cause an error

error: 'Impl' is a private member of 'MyClass'
template<class T, class Impl = typename T::Impl> class Pimpl
                                           ^

This may seems reasonable, but if I replace Pimpl<MyClass> m_impl; by Pimpl<MyClass, Impl> m_impl; in MyClass then clang++ build without issue and hapily create an instance of the "private" class inside Pimpl::Pimpl().

So is using a private inner class as template parameter default value a valid behaviour (and there is an issue in clang) ? Or is it forbidden (and gcc is too permissive) ? Or is is not specified ?

This question How can I use a private inner template class as a parameter in a template specialization? is similar but context (template specialization) and answer ("the template is never actually instantiated") does not seem relevant here.

Tested with clang 10.0.0 and gcc 9.4.0. Code uses c++17.


Solution

  • I believe clang conforms to the standard here. According to [temp.arg]/3,

    The name of a template-argument shall be accessible at the point where it is used as a template-argument. ... For a template-argument that is a class type or a class template, the template definition has no special access rights to the members of the template-argument.

    The implicit instantiation of Pimpl<MyClass, MyClass::Impl> inside MyClass is invalid actually.