Floyd's cycle-finding algorithm I am writing a piece of code for some students and I came up with the following implementation of Floyd's algorithm for finding cycles in linked lists.
I was wondering if there are ways I can improve what I have so far such that the result is

more C++ idiomatic
does not contain dumb mistakes associated with the generic code

template &lt;typename T&gt;
struct Node {
    T val;
    Node* next;
    Node() = default;
    Node(T x)
        : val(x)
        , next(nullptr)
    {
    }
};

template &lt;typename T&gt;
Node&lt;T&gt;* detect_cycle_constant_time(Node&lt;T&gt;* head)
{
    Node&lt;T&gt; *n1, *n2;
    n1 = n2 = head;

    while (n1 &amp;&amp; n2) {
        n1 = n1-&gt;next;
        n2 = n2-&gt;next;
        if (n2)
            n2 = n2-&gt;next;
        else
            break;

        if (n1 == n2)
            break;
    }

    // second phase of Floyds's algorithm
    if (n1 == n2) {
        n2 = head;
        while (n1 != n2) {
            n1 = n1-&gt;next;
            n2 = n2-&gt;next;
        }
        return n1;
    }
    return nullptr;
}
General
I found no &quot;dumb mistakes associated with generic code&quot;. Good job.
Still, there are some points which can be improved.
(Aren't there always?)

The first point is encapsulation.
Letting nodes float around free, instead of keeping them in their dedicated container, or (rarely) evacuating them to a handle for re-insertion, is not recommended.
Admittedly, a properly written container won't suddenly sprout a cycle, so there you are.
Also, it would detract from the point of your code, so let's leave it alone this time.
Node
Node's default ctor won't initialize its members. The fix is easy:
Just add in-class initializers.
Doing so even allows you to simplify your second constructor, removing the second mem-initializer.
That is, if Node should even have anything but its data members. I personally prefer to have my Node's not getting in the way when implementing my containers.
Floyd's algorithm
The name detect_cycle_constant_time() is a bald-faced lie. The algorithm needs linear time in the number of nodes.
Doing an early return would simplify your code. As you don't allocate any resources, there goes the only argument against. Doing data-flow analysis is much more involved.
There are more loops than the humble while-loop. Sometimes, they are even useful.
Using auto would slightly simplify your local variable definitions.
Whether to use class or typename for template arguments is a matter of taste. All else being equal, I prefer brevity.
Modified Code
template &lt;class T&gt;
struct Node {
    T val = T();
    Node* next = nullptr;
    Node() = default;
    Node(T x) : val(x) {}
};

template &lt;class T&gt;
Node&lt;T&gt;* detect_cycle_floyd(Node&lt;T&gt;* head)
{
    auto n1 = n2 = head;

    do {
        if (!n2)
            return n2;
        n1 = n1-&gt;next;
        n2 = n2-&gt;next;
        if (!n2)
            return n2;
        n2 = n2-&gt;next;
    } while (n1 != n2);

    // second phase of Floyds's algorithm
    for (n2 = head; n1 != n2; n2 = n2-&gt;next)
        n1 = n1-&gt;next;
    return n1;
}