Zizheng Tai
Zizheng Tai

Reputation: 6636

How to use boost::intrusive_ptr for private nested class within class template

Suppose I have a list class:

template<typename T>
class list {
    ...

private:
    class node {
        ...

    private:
        std::size_t refcount_;

        // friends of node because accessing private member refcount_
        friend void intrusive_ptr_add_ref(const node* p) noexcept;
        friend void intrusive_ptr_release(const node* p) noexcept;            
    };

    // friends of list because accessing private nested class node
    friend void intrusive_ptr_add_ref(const node* p) noexcept;
    friend void intrusive_ptr_release(const node* p) noexcept;

    boost::intrusive_ptr<node> node_{new node};
};

template<typename T>
void intrusive_ptr_add_ref(const typename list<T>::node* p) noexcept
{ ... }

template<typename T>
void intrusive_ptr_release(const typename list<T>::node* p) noexcept
{ ... }

list<int> xs;  // error

The code above doesn't compile. The error was undefined symbols for intrusive_ptr_add_ref(list<int>::node const*) and intrusive_ptr_release(list<int>::node const*).

I think the problem is probably that I'm declaring non-template functions as friends in list and node, but what I defined are function templates. So what's the correct way to do this?

Upvotes: 1

Views: 377

Answers (1)

sehe
sehe

Reputation: 393457

This is one of those occasions where inline friend definitions shine:

Live On Coliru

#include <iostream>
#include <boost/intrusive_ptr.hpp>

template<typename T> class list {
    class node {
        std::size_t mutable refcount_;

        // friends of list because accessing private nested class node
        friend void intrusive_ptr_add_ref(node const* p) noexcept {
            p->refcount_ += 1;
        }
        friend void intrusive_ptr_release(node const* p) noexcept {
            if (--p->refcount_)
                return;
            std::cout << "freeing node " << static_cast<void const*>(p) << "\n";
        }
    };

    boost::intrusive_ptr<node> node_{new node};
};

int main() {
    list<int> xs;
}

Prints

freeing node 0x19b7c20

or similar

BONUS POINTS

If you want to go the verbose route, I'd suggest the sanest way is to have the base-template parameterized on the Node type, not the list-element (because partial specializations don't go with function templates).

Here's something that also works:

Live On Coliru

template <typename Node, typename = typename Node::is_my_list_impl_nodetype> void intrusive_ptr_add_ref(Node const*) noexcept;
template <typename Node, typename = typename Node::is_my_list_impl_nodetype> void intrusive_ptr_release(Node const*) noexcept;

template<typename T> class list {
    class node {
        using is_my_list_impl_nodetype = std::true_type;

        std::size_t mutable refcount_;

        // friends of list because accessing private nested class node
        friend void intrusive_ptr_add_ref<node, std::true_type>(node const* p) noexcept;
        friend void intrusive_ptr_release<node, std::true_type>(node const* p) noexcept;
    };

    boost::intrusive_ptr<node> node_{new node};
};

template<typename Node, typename>
void intrusive_ptr_add_ref(Node const* p) noexcept {
    p->refcount_ += 1;
}

template<typename Node, typename>
void intrusive_ptr_release(Node const* p) noexcept {
    if (--p->refcount_)
        return;
    std::cout << "freeing node " << static_cast<void const*>(p) << "\n";
}

The whole SFINAE on is_my_list_impl_nodetype is to prevent an open template from creating ambiguous overloads if you have more intrusive pointer usages in your translation unit that use other addref/release methods.

Upvotes: 1

Related Questions