c++friend-functionaccess-specifiertemplate-classes

friend function is not a friend of all template instances?


In the following example I try to access the private member function subscribe() from a templated class type from within its friend function. However it appears that the friend function is only befriended in one instantiation of the class and not the other, hence yielding a "private within this context"-error. Check out the following:

Demo

#include <cstdio>

template <typename T>
class my_class
{
    template <typename... Ts>
    friend auto observe(my_class<Ts>... args) {
        ([&]{ args.subscribe(); }(), ...);
    }

    void subscribe() {
        printf("subscribed!\n");
    }
};

int main()
{
    my_class<int> a{};
    my_class<bool> b{};
    observe(a,b);
}

Error:

<source>:19:20:   required from here
<source>:7:17: error: redefinition of 'template<class ... Ts> auto observe(my_class<Ts>...)'
    7 |     friend auto observe(my_class<Ts>... args) {
      |                 ^~~~~~~
<source>:7:17: note: 'template<class ... Ts> auto observe(my_class<Ts>...)' previously declared here
<source>: In instantiation of 'auto observe(my_class<Ts>...) [with Ts = {int, bool}; T = int]':
<source>:20:12:   required from here
<source>:8:29: error: 'void my_class<T>::subscribe() [with T = bool]' is private within this context
    8 |         ([&]{ args.subscribe(); }(), ...);
      |               ~~~~~~~~~~~~~~^~
<source>:11:10: note: declared private here
   11 |     void subscribe() {
      |          ^~~~~~~~~

Is this actually correct by the compiler?


Solution

  • Yes, this is correct.

    my_class<int> and my_class<bool> are separate classes, and everything inside them will get defined separately for each instantiation of the template. Normally this is fine, because the stuff inside the classes is class members, so i.e. my_class<int>::subscribe and my_class<bool>::subscribe are different functions.

    But observe is not a class member, so the fact that it gets defined by both my_class<int> and my_class<bool> is a violation of the One Definition Rule.

    What you need to do is move the definition of observe outside of my_class so that it only gets defined once:

    #include <cstdio>
    
    template <typename T>
    class my_class
    {
    public:
        template <typename... Ts>
        friend auto observe(my_class<Ts>... args);
    
        void subscribe() {
            printf("subscribed!\n");
        }
    };
    
    template <typename... Ts>
    auto observe(my_class<Ts>... args) {
        ([&]{ args.subscribe(); }(), ...);
    }
    
    int main()
    {
        my_class<int> a{};
        my_class<bool> b{};
        observe(a,b);
    }
    

    Demo