is it ok to specialize std::numeric_limits<T> for user-defined number-like classes?

Solution 1:

Short answer:

Go ahead, nothing bad will happen.

Long answer:

The C++ standard extensively protects the ::std namespace in C++11 17.6.4.2.1, but specifically allows your case in paragraphs 1 and 2:

The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.

[...] A program may explicitly instantiate a template defined in the standard library only if the declaration depends on the name of a user-defined type and the instantiation meets the standard library requirements for the original template.

The older C++03 has a similar definition in 17.4.3.1/1:

It is undefined for a C++ program to add declarations or definitions to namespace std or namespaces within namespace std unless otherwise specified. A program may add template specializations for any standard library template to namespace std. Such a specialization (complete or partial) of a standard library template results in undefined behavior unless the declaration depends on a user-defined name of external linkage and unless the specialization meets the standard library requirements for the original template.

After getting past this fundamental stepping stone, you already pointed out, C++03 18.2.1/4 forbids specializations of ::std::numeric_limits for certain types:

Non-fundamental standard types, such as complex (26.2.2), shall not have specializations.

The more current C++11 18.3.2.1/4 has a slightly different wording:

Non-arithmetic standard types, such as complex<T> (26.4.2), shall not have specializations.

Both of these formulations however allow specializations for non-standard types, which T is, since you defined it yourself (as @BoPersson already pointed out in the comments).

Caveats

C++11 18.3.2.3/1 hints that you should (but does not require you to) ensure that your specialization has all members.

Also, you may wish to ensure that C++11 18.3.2.3/2 is not violated by your specialization:

The value of each member of a specialization of numeric_limits on a cv-qualified type cv T shall be equal to the value of the corresponding member of the specialization on the unqualified type T.

Which essentially means, that if you wish to specialize it for T, you should also do so for T const, T volatile and T const volatile.

Solution 2:

Just an example:

namespace std {
    template<> class numeric_limits<Temperature> {
    public:
       static Temperature lowest() {return Temperature(-273.15f);};
       // One can implement other methods if needed
    };
}