Reduce compilation times and dependencies using friend class

107 views Asked by At

In one of my projects I have a tree data structure, that might contain values of a generic type T.

In order to reduce compilation times and dependencies I tried to move implementation details from the tree node class Node to class NodeImpl. The internals of Node are private and should be only accessed at defined entry points, for example by the getName function.

Only people that are interested in using getName should include NodeImpl in their source files. This is how I think to reduce my compilation times and dependencies.

But for some reason the following three toys classes will not compile. It says no access to private members. What I'm doing wrong?

File main.cpp:

#include <iostream>
#include "Node.h"
#include "NodeImpl.h"

int main(int argc, char** args) {
    Node<int> n("Test", 2);
    std::cout << getName(n) << std::endl;
}

File Node.h:

#pragma once
#include <string>

template<typename T>
class NodeImpl;

template<typename T>
class Node {
public:
    typedef T value_type;
    Node(const std::string& name, const T& value) : name(name), value(value) {}

private:
    std::string name;
    T value;
    friend class NodeImpl<T>;
};

File NodeImpl.h:

#pragma once
#include "Node.h"

template<typename T>
std::string getName(Node<T>& n);

template<typename T>
class NodeImpl {
    NodeImpl(Node<T>& node) : mNode(node) {
    }
    Node<T>& mNode;

    std::string name() {
        return mNode.name;
    }

    friend std::string getName(Node<T>& n);
};

template<typename T>
std::string getName(Node<T>& n) {
    auto x = NodeImpl<T>(n);
    return x.name();
}
1

There are 1 answers

1
Angew is no longer proud of SO On BEST ANSWER

The warning produced by GCC gives insight:

warning: friend declaration 'std::__cxx11::string getName(Node<T>&)' declares a non-template function [-Wnon-template-friend]
 friend std::string getName(Node<T>& n);
                                      ^
note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) 

In other words, your class NodeImpl<int> was befriending a global function std::string getName(Node<int> &), which is unrelated to the function std::string getName<int>(Node<int> &) instantiated from the function template template <class T> std::string getName(Node<T> &) for T = int.

So the correct solution is this:

template<typename T>
class NodeImpl {
    // ... as before ...

    friend std::string getName<T>(Node<T>& n);
};

This [live example] shows that this solution indeed works.