Unique compile time (constexpr) type ID without RTTI
I'm convinced that your design will work correctly. Uniqueness of static variables is guaranteed, and they won't be optimized away if you define and odr-use them (taking the address of a variable is odr-using it). Running it through stack-overflow would be a good idea though, because it will probably catch the eye of one of those 200K+ reputation guru who answer all the tricky language questions.

What I can't understand is what you're trying to achieve with this. The only useful property of this compile-time id is its uniqueness, so it doesn't achieve anything more than the type itself: what is the difference between if (type_id&lt;A&gt;() == type_id&lt;B&gt;()) and if (std::is_same_v&lt;A, B&gt;)? I would argue that it achieves a lot less, because types have other useful traits (for a partial but ready-made list, just take a look at the type_traits header in the standard library).

Besides, your id generator isn't truly generic: type_id&lt;int&amp;&gt;() won't compile because a pointer can't point to a reference (thus static const int&amp;* const id is illegal). Of course there's always the possibility to remove the reference, but it means that you'll use type_traits to make your type_id work -so it underlines that you should use well-established, standard type traits instead of this new compile-time type_id.

I'd suggest, if it is possible, that you post here a larger part of your code,
 containing use cases, and let our collective brain work on the solution of the broader problem.



Edit: I understand now better what you want to do. I really like the simplicity of use, and I find the hack very clever. Actually, if it was uglier, limitations like no-references-allowed wouldn't matter much, because everyone would see it as work-around. But since it seems so fluent, it could quickly become pervasive in the code; then limitations matter, all the more when they result in obscure error messages.

Anyway, I believe I would have come up with a less ambitious design, which would also be easier to maintain and understand (this concrete implementation relies on C++17 but it wouldn't be to difficult to implement in previous standards):

#include &lt;iostream&gt;
#include &lt;vector&gt;

template &lt;typename... Types&gt;
struct Type_register{};

template &lt;typename Queried_type&gt;
constexpr int type_id(Type_register&lt;&gt;) { static_assert(false, "You shan't query a type you didn't register first"); return -1; }

template &lt;typename Queried_type, typename Type, typename... Types&gt;
constexpr int type_id(Type_register&lt;Type, Types...&gt;) {
    if constexpr (std::is_same_v&lt;Type, Queried_type&gt;) return 0;
    else return 1 + type_id&lt;Queried_type&gt;(Type_register&lt;Types...&gt;());
}

int main() {
   Type_register&lt;int, float, char, std::vector&lt;int&gt;, int&amp;&gt; registered_types;
   constexpr auto test1 = type_id&lt;int&amp;&gt;(registered_types);
   constexpr auto test2 = type_id&lt;int*&gt;(registered_types);
}