2

In modern C++ it is often recommended to use unique_ptr when dealing with binary trees to make the ownership of subtrees explicit. For instance, Elements of Programming Interviews recommends:

template <typename T>
struct Node {
  T data;
  unique_ptr<Node<T>> left, right;
};

I'm just learning C++11 features, and I'm wondering what is the most convenient way to initialize a binary tree corresponding to a certain structure. My use case is to write unit tests for specific trees. For example, I want to generate this tree:

    5
   / \
  3   4
 / \
1   2

The following does work, but it is really cumbersome:

// first attempt: temporary variables & high syntactic noise
unique_ptr<Node<int>> tmp_n1(new Node<int>{1, nullptr, nullptr});
unique_ptr<Node<int>> tmp_n2(new Node<int>{2, nullptr, nullptr});
unique_ptr<Node<int>> tmp_n3(new Node<int>{3, move(tmp_n1), move(tmp_n2)});
unique_ptr<Node<int>> tmp_n4(new Node<int>{4, nullptr, nullptr});
unique_ptr<Node<int>> root(new Node<int>{5, move(tmp_n3), move(tmp_n4)});

What I was hoping to achieve is to get rid of the temporary variables, and initialize the tree in one nested statement. It would be nice if the code structure would resemble the tree structure. However, the following attempt fails with a "could not convert" error:

// second attempt: nested, but still a lot of syntax noise
unique_ptr<Node<int>> root(new Node<int>{5,
  new unique_ptr<Node<int>>(new Node<int>{3,
    new unique_ptr<Node<int>>(new Node<int>{1, nullptr, nullptr}),
    new unique_ptr<Node<int>>(new Node<int>{2, nullptr, nullptr})
  }),
  new unique_ptr<Node<int>>(new Node<int>{4, nullptr, nullptr})
});

Any ideas how to write such tree initializations in a syntactically clean, concise, flexible way?

bluenote10
  • 23,414
  • 14
  • 122
  • 178
  • 2
    You are creating `unique_ptr` with `new`. It can not convert pointer to `unique_ptr` to `unique_ptr`. Try without `new` http://cpp.sh/8upu –  Sep 16 '15 at 07:04
  • @Satus: You are right, it works without `new` and it also makes much more sense. Stupid mistake, thanks for pointing it out. – bluenote10 Sep 16 '15 at 07:25
  • I also tried to replace `unique_ptr>` with initializer list to reduce syntax, but it appears to be defined as explicit. –  Sep 16 '15 at 07:29

2 Answers2

3

Here's a solution that uses C++14 make_unique with default parameters set to nullptr for the left and right subtrees, in order to avoid the raw new:

#include <iostream>
#include <memory>

template<class T>
struct Node;

template<class T>
using node_ptr = std::unique_ptr<Node<T>>;

template<class T>
struct Node 
{
    T data;
    node_ptr<T> left, right;

    Node(T const& value, node_ptr<T> lhs, node_ptr<T> rhs)
    :
        data(value), left(std::move(lhs)), right(std::move(rhs))
    {}
};

template<class T>
auto make_node(T const& value, node_ptr<T> lhs = nullptr, node_ptr<T> rhs = nullptr)
{
    return std::make_unique<Node<T>>(value, std::move(lhs), std::move(rhs));    
}

template<class T>
void print_tree(Node<T> const& node)
{
    std::cout << "{ ";
    std::cout << node.data;
    if (node.left) {
        std::cout << ", "; 
        print_tree(*(node.left));
    }
    if (node.right) {
        std::cout << ", "; 
        print_tree(*(node.right));
    }
    std::cout << " }";
}

int main()
{
    auto const root = make_node(
        5, 
        make_node(
            3, 
            make_node(1), 
            make_node(2)
        ), 
        make_node(4)
    );    
    print_tree(*root);
}

Live Example that prints the tree as well.

Update: thanks to the comment by @Jarod42, I changed the signature of print_tree to take a Node<T> const& so that it is now const-correct and you don't have to type .get() anywhere. I also made a template-alias node_ptr<T> to have more concise notation for unique_ptr<Node<T>> in the implementation.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • I was wondering why you have added a manual constructor. But it looks like `make_unique` in general does not work with the default constructor, right? – bluenote10 Sep 16 '15 at 08:03
  • 1
    @bluenote10 `make_unique(args)` calls `T(args)` (with parentheses), whereas you can only aggregate-initialize a `struct` with `T{args}` (with braces). So you have to provide your own constructor. The default constructor is something else, btw, that allows you to do `T()` (no arguments at all). – TemplateRex Sep 16 '15 at 08:05
  • What a great solution! :) I completely forgot about `make_unique`. –  Sep 16 '15 at 08:12
  • 1
    @Satus it's C++14, but easy to write in C++11 as well – TemplateRex Sep 16 '15 at 08:22
  • @Jarod42 thanks, for `node` equal to `nullptr`, getting `->data` would be UB. Updated! – TemplateRex Sep 16 '15 at 08:55
2

Thanks to @Satus' comment I was able to come up with some (now working) helper function:

template <typename T>
unique_ptr<Node<T>> new_node(const T& data, 
                             unique_ptr<Node<T>> left = nullptr,
                             unique_ptr<Node<T>> right = nullptr) {
  return unique_ptr<Node<int>>(new Node<int>{data, move(left), move(right)});
}

This allows to construct a tree like this:

auto root =
  new_node(5,
    new_node(3,
      new_node(1),
      new_node(2)),
    new_node(4)
  );

I'm still not sure if this is what a more experienced C++11 programmer would do, but for me it is a step in the right direction.

bluenote10
  • 23,414
  • 14
  • 122
  • 178
  • 2
    One possible improvement would be to get an implementation of C++14's `std::make_unique` to avoid repeating the types in the `new_node` definitions. Have a look at [this question](http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding) for some examples. – TartanLlama Sep 16 '15 at 07:34