C++: Why does numeric_limits work on types it does

2020-05-25 07:55发布

问题:

I have created my own type, without any comparator, and without a specialization of std::numeric_limits. Despite that, for some reason, std::numeric_limits<MyType> compiles fine. Why did the c++ standards committee define the numeric_limits template such that it is valid for all types, including non-numeric types??

Example code below:

#include <iostream>
#include <limits>
using namespace std;

// This is an int wrapper that defaults to 666 instead of 0
class A {
public:
    int x;
public:
    A() : x(666) {}
};

int main() {
    A a = std::numeric_limits<A>::max();
    A b = std::numeric_limits<A>::max();

    std::cout << a.x << "\n" << b.x;
    // your code goes here
    return 0;
}

回答1:

The class template std::numeric_limits was added as a replacement for the macros from <limits.h> before template meta programming was a thing: it was in the pre-standard publicly circulated drafts (~1995). Template meta programming was invented by Erwin Unruh around the Stockholm meeting (July 1996). At this point nobody thought whether it could be detectable that a class template is defined. Instead, std::numeric_limits<T>::is_specialized would indicate (at compile-time) whether the class template is specialized and meaningful for type T. The various member were defined to use a reasonable default which would make it likely to get the code compiled although generic would be implemented such that it doesn't use any of the values for types which are not specialized.

With std::numeric_limits being specified like that in the C++ standard it wouldn't change without a very good reason: any change would likely break somebody's code - even if this code could be done better with now discovered technology (some of which was genuinely unavailable with C++98). The committee wouldn't design the traits like this now: the type traits in <type_traits> are stand-alone traits - although generally still defined for all viable types with a suitable default. However, there is also no reason to change std::numeric_limits in a breaking way as the current definition does work.

Note that not all members of std::numeric_limits<T> are valid for all types T. For example use of std::numeric_limits<T>::max() will fail to compile if T's default constructor is unaccessible, unavailable, or deleted. So, you'd be best off guarding access to any of the members on whether the class template is specialized (using C++17):

template <typename T>
void f() {
    if constexpr (std::numeric_limits<T>::is_specialized) {
        // use of std::numeric_limits<T>::max(), min(), etc.
    }
    else {
        // implement the rquired functionality differently
    }
}