Building a minimal-height BST from a monotonically increasingly ordered array structure
Simplified Node type

This constructor:

Node() : left(), right() {}


Can be replaced with:

constexpr Node() noexcept
    : data{}
{}


The default constructor of std::unique_ptr&lt;&gt;  will initialize its contained pointer to nullptr, so you do not need to explicitly call it.

All of your classes' data members have non-throwing default constructors. Thus, you can safely mark yours as noexcept.

We can mark this constructor as constexpr, because similarly, all of your data member's default constructors are constexpr. This can be useful if you want to define a static compile-time special value, such as the empty node value.



This constructor:

Node(int data) : data(data), left(), right() {}


Can be replaced with:

constexpr Node(int const data) noexcept
    : data(data)
{}



We mark the parameter as const, because it is not modified.
We remove the redundant calls to the default constructors of left and right.
We mark it as constexpr and noexcept.
We use new lines for readability.




Finally, since you can live with initializing data with zero, you can replace both constructors with a single one:

constexpr Node(int const data = 0) noexcept
//                          ^^^^^ default value
    : data(data)
{}


Simplified calls

Your get_midpoint_iterator() function can be simplified. Here is how it currently looks:

template &lt;typename It&gt;
It get_midpoint_iterator(It start, It end) {
    auto midpoint = std::distance(start, end) / 2;
    It mid = start;
    std::advance(mid, midpoint);
    return mid;
}


These statements:

It mid = start;
std::advance(mid, midpoint);
return mid;


Can be replaced with:

return std::next(mid, midpoint);


View the documentation here: http://en.cppreference.com/w/cpp/iterator/next



Since your function's body now looks like this:

auto midpoint = std::distance(start, end) / 2;
return std::next(start, midpoint);


You can simply erase midpoint and put the calculation at the function call location:

return std::next(start, std::distance(start, end) / 2);


You end up with a concise function that is guaranteed copy elision in C++17.

template &lt;typename It&gt;
It get_midpoint_iterator(It start, It end) {
    return std::next(start, std::distance(start, end) / 2);
}


As @miscco points out, this obfuscates the operation slightly, so you might also want to add a comment indicating that std::distance(start, end) / 2 is the midpoint.



Your create_bst_helper() function currently initializes a std::unique_ptr&lt;&gt; in the following fashion:

root = std::move(std::unique_ptr&lt;Node&gt;(new Node(*midpoint)));


This can be more clearly expressed with:

root = std::make_unique&lt;Node&gt;(*midpoint);




Your create_bst_helper() function currently creates a std::unique_ptr&lt;&gt; instance by assigning nullptr to it:

std::unique_ptr&lt;Node&gt; bst = nullptr;


The default constructor of std::unique_ptr&lt;&gt; already does so:

std::unique_ptr&lt;Node&gt; bst; // equivalent to `= nullptr;`