Search code examples
c++templatesc++17friend

Making a class template declared in an outer anonymous namespace a friend


Clang refuses to compile the following code (godbolt) while gcc sees no problem with it. Clang error message is shown below line marked (2):

namespace  // Overall anonymous namespace is required and cannot be removed.
{

template <typename T>
struct Friend;

namespace ns
{

class Secret
{
    template <typename T>
    friend struct Friend;     // (1)

    static void foo() {}
};

} // namespace ns

template <typename T>
struct Friend
{
    void bar()
    {
        ns::Secret::foo();   // (2)
        // Error at line (2):
        // 'foo' is a private member of '(anonymous namespace)::ns::Secret'
    }
};

} // anonymous namespace

The reason, I assume, is that line (1) is treated as a declaration of a new class template struct ns::Friend<T> rather than a reference to ::(anonymous namespace)::Friend<T>.

The questions are:

  1. Who's right -- clang or gcc?
  2. If clang is right, then is there a way to make it understand that line (1) doesn't introduce new class template, but refers to existing one?

Solution

  • Yes, you are right that simply friend struct Friend is a declaration of a templated class ::(anonymous namespace)::ns::Friend. Both compilers are right, as when you attempt to use Friend<T>::bar() gcc will complain about access as well (clang just checks a lot earlier than gcc)

    To specify the class template in the global namespace, you must write that out:

        template <typename T>
        friend struct ::Friend;     // (1)
    

    This works for GCC, but clang seems broken and doesn't find Friend in the global namespace.

    These two workarounds seem to work:

    // Make the entire unnamed namespace an inline namespace
    inline namespace
    {
    // ...
    }
    
    
    // Add an explicit using directive to help clang find Friend in `::`
    namespace {
    
    template<typename T>
    struct Friend;
    
    }
    using ::Friend;
    namespace {
    namespace ns {
    // ...
    }
    }