Reputation: 6636
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
Reputation: 393457
This is one of those occasions where inline friend definitions shine:
#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
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:
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