1

I have a datastructure similar to a list:

template<typename T>
struct node {
  T val;
  unique_ptr<node<T>> next;
};

and a simple traversal function:

template<typename T, typename UnaryOp>
void traverse(node<T>& list, UnaryOp op) {
  node<T>* current = &list;
  while(current) {
    op(*current);
    current = current->next.get();
}

I now need a const and a non-const version of the traverse function that accepts either a const node<T>& list or a node<T>& list, depending on the context, preferably avoiding code duplication. How could this be achieved?

fuji
  • 1,173
  • 1
  • 10
  • 27
  • What is it that you need to be `const` here? The traversal function doesn't have to be a member at all, so you seem to be asking for something else. – thokra Sep 02 '16 at 09:02
  • The `list` parameter in the signature. I would need a ``const node& list`` and a ``node& list``. One example would be to implement a function ``size_t size(const node& list) {...}`` – fuji Sep 02 '16 at 11:08
  • Clarified the question, to reflect my comment. – fuji Sep 02 '16 at 11:20

1 Answers1

1

A possible solution is creating a static template function that takes *this as a forwarding reference:

class node
{
private:
    template <typename TSelf>
    static void traverse_impl(TSelf& self, node<T>& list, UnaryOp op)
    {
        node<T>* current = &list;
        while(current)
        {
            op(*current);
            current = current->next.get();
        }
    }

public:
    void traverse(node<T>& list, UnaryOp op)
    {
        traverse_impl(*this, list, op);
    }

    void traverse(node<T>& list, UnaryOp op) const
    {
        traverse_impl(*this, list, op);
    }
};

This works because of template argument deduction rules - in short, TSelf will accept both const and non-const references.

If you need to access members of this inside traverse_impl, use self.member.

Additionally, you can use std::conditional or similar facilities inside traverse_impl to do different things depending on the const-ness of TSelf. You can also use a forwarding reference (TSelf&&) and handle the case where this is being moved thanks to ref-qualifiers and perfect-forwarding.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • What excatly is this solving? Why is `traverse_impl` not a free function? Why is `traverse` dispatching to a potential free function with a `const` and non-`const` `TSelf`, although the the function does not use `self` anywhere? It's basically tag dispatching, but for what? It doesn't change the way you traverse and what `UnaryOp` does with the nodes in`list` in any way. Am I missing something? – thokra Sep 02 '16 at 09:25
  • @thokra in the OP's example, this solution is overkill - a free function would work as well. I'm answering the **more general** *"how to avoid const/non-const member repetition?"* answer. Using a `static` template function allows the implementation to be in the class *(where it should be)*. Using a `TSelf` parameter allows the implementation to access members if required, and to easily check `const`-ness by using `type_traits`. I'm confident that it pays off to be as generic as possible early, instead of rewriting huge portions of code later. – Vittorio Romeo Sep 02 '16 at 09:30
  • That's fine, but I don't understand the conclusion "where it should be" - why? I see no benefit of it being a static member function - at all. It could just as well be a free function in an unnamed namespace in the implementation file, thus being completely hidden from the user of `node` and the rest of the world which doesn't care about the implementation. You are generic, yes, but IMHO, you're also unnecessarily verbose and being as generic as possible as early possible, in my experience, can go very, very badly if not done properly. – thokra Sep 02 '16 at 10:25
  • BTW, what would be really generic is to provide a `const` and non-`const` iterator type for `node` and then simply use something like `std::for_each` to apply `UnaryOp` to all nodes of the forward list. – thokra Sep 02 '16 at 10:27
  • It's subjective - `traverse_impl` is simply an implementation detail for the `node` class. Implementing it as a `private` `static` function is as appropriate as implementing it as a *"hidden"* free function. Also, I would gladly pay the "verbosity" cost in order to be more generic... and anything can go badly if not done properly. Using a "generic" `template` is not an excuse for not thinking about the interface/architecture of what you're implementing – Vittorio Romeo Sep 02 '16 at 10:37
  • 1
    The iterator-based solution sounds good and generic as well, but it doesn't really answer the *"how to avoid repetition between `const` and non-`const` methods of a class?"*. Personal preference also has a role there, though - you could decide to implement class functionality as higher-order functions instead of creating your own iterator classes (it has its own benefits/drawbacks, obviously). – Vittorio Romeo Sep 02 '16 at 10:39