Binary Search Tree implementation with unique pointers
Observations
You pass data by value. This is fine for small data types like int. But imagine that T is some huge object that is very expensive to copy. You should think of passing the data by reference to avoid any intermediate copies. When you get a bit more advanced think about passing by r-value reference to allow you to move the objects.
For your traversal you pass a function pointer. This is a bit limiting. Normally you would templateze the function to allow you to pass any function like object (function pointer/functor/lambda/std::function) etc.
Yes you need recursion when traversing trees. But you don't need it everywhere. There are a couple of places where a simple loop would be sufficient.
I can see why you use std::unique_ptr. But in my view a tree is a container and should manage its own memory. So I would have simply used Node* inside data. The code is not that much harder to write in this context. BUT I don't have any real issue with std::unique_ptr.
Code
Insert by const reference
void insert(T const&amp; data)
{
    _insert(root, data);
}
_insert(std::unique_ptr&lt;node&gt;&amp; curr, T const&amp; data)
{
    if (curr.get() == nullptr) {
        curr.reset(new node(data));
        return;
    }

    auto&amp; next = (data &lt; curr-&gt;data) ? curr-&gt;left : curr-&gt;right;
    _insert(next, data);
}

Insert by r-Value reference
void insert(T&amp;&amp; data)
{
    _insert(root, std::forward&lt;T&gt;(data));
}
_insert(std::unique_ptr&lt;node&gt;&amp; curr, T&amp;&amp; data)
{
    if (curr.get() == nullptr) {
        curr.reset(new node(data));
        return;
    }

    auto&amp; next = (data &lt; curr-&gt;data) ? curr-&gt;left : curr-&gt;right;
    _insert(next, std::forward&lt;T&gt;(data));
}

Emplace into node.
// This calls T constructor only when you construct the node itself.
template&lt;typename... Args&gt;
void emplace(Args const&amp;... args)
{
    _emplace(root, args...));
}
template&lt;typename... Args&gt;
_emplace(std::unique_ptr&lt;node&gt;&amp; curr, Args const&amp;... args)
{
    if (curr.get() == nullptr) {
        curr.reset(new node(args...));
        return;
    }

    auto&amp; next = (data &lt; curr-&gt;data) ? curr-&gt;left : curr-&gt;right;
    _insert(next, args...);
}
template&lt;typename... Args&gt;
node::node(Args const&amp;... args)
    : data(args...)  // Data of type T constructed in place
    , left(nullptr)
    , right(nullptr)
{}

Using a function like object to traverse the tree:
template&lt;typename F&gt;
void traverse(const &amp; action)
{
    _traverse(root, action);
}
template&lt;typename F&gt;
void _traverse(std::unique_ptr&lt;node&gt;&amp; curr, F const&amp; action);


// outside class

template&lt;typename T&gt;
template&lt;typename F&gt;
void Btree&lt;T&gt;::_traverse(std::unique_ptr&lt;node&gt;&amp; curr, F const&amp; action)
{
    if (curr == nullptr)
        return;
    _traverse(curr-&gt;left, action);
    action(curr-&gt;data);
    _traverse(curr-&gt;right, action);
}

int main()
{
     Btree&lt;int&gt;  tree;
     tree.traverse([](int x){std::cout &lt;&lt; x &lt;&lt; &quot; &quot;;});
}