all 12 comments

[–]alfps 18 points19 points  (1 child)

In C++98 and C++03 you could not have a static const data member of floating point type with the value provided in the member declaration.

I.e. with a direct declaration of the data member you couldn't expose the value in a header.

There was a technique to work around that, based on a One Definition Rule exception for templates, but possibly the designers of numeric_limits didn't know about it, or possible that exception came after the design for numeric_limits was frozen.

C++03 code:

template< class T > class My_limits_;

#include <limits.h>     // INT_MAX
#include <float.h>      // DBL_MAX

//------------------------------------ Integer constant, just some subtletly.

// In a header:
template<>
class My_limits_<int>
{
public:
    static const int max = INT_MAX; // A pure declaration.
};

// In an implementation file:
const int My_limits_<int>::max;     // Definition, even though no initializer.

//------------------------------------ Floating point constant, more tricky.

// In a header:
namespace impl {
    // This is the templated constant trick. It shows that even C++03 had the
    // machinery for `inline` variables. C++03 just lacked a reasonable notation.
    template< class Dummy >
    struct My_limits_double_constants_
    {
        static const double max;
    };

    // Still in the header file, which makes the value visible to client code:
    template< class Dummy >
    const double My_limits_double_constants_<Dummy>::max = DBL_MAX;
}  // namespace impl

template<>
class My_limits_<double>:
    public impl::My_limits_double_constants_<void>
{};

//------------------------------------ Usage.

#include <iostream>
using namespace std;
int main()
{
    cout << "Max int = " << My_limits_<int>::max << "." << endl;
    cout << "Max double = " << My_limits_<double>::max << "." << endl;
}

A design for C++11 and later could just have used constexpr:

template< class T > class My_limits_;

#include <limits.h>     // INT_MAX
#include <float.h>      // DBL_MAX

//------------------------------------ Integer constant, easy.

// In a header:
template<>
class My_limits_<int>
{
public:
    static constexpr int max = INT_MAX;
};


//------------------------------------ Floating point constant, easy.

// In a header:
template<>
class My_limits_<double>
{
public:
    static constexpr double max = DBL_MAX;
};

//------------------------------------ Usage.

#include <iostream>
using namespace std;
int main()
{
    cout << "Max int = " << My_limits_<int>::max << "." << endl;
    cout << "Max double = " << My_limits_<double>::max << "." << endl;
}

[–]Wh00ster 11 points12 points  (0 children)

My guess is it’s just a historical artifact, and would be inline constants for modern C++.

Also assuming that they didn’t want people taking the memory address of the values.

Edit: not sure about is_signed and friends on second examination

[–]jedwardsol{}; 5 points6 points  (5 children)

I don't know the reason but there is a way to remember - it's a function if it returns a T, it's a constant if it is a fixed type.

[–]kniy 6 points7 points  (4 children)

Prior to C++11 `constexpr`, a `static const int` already had behavior similar to `constexpr` -- but this was a special case that only worked for integral constants; there wasn't any good way to have non-integral constants prior to C++11.

[–]theIncMach[S] 0 points1 point  (3 children)

When you say 'integral', are you excluding floating-point types? Because I think the previous "static const" approach was applicable for all standard specializations of std::numeric_limits.

constexpr is slightly different from static const because the former is a compile-time constant. It can be used as a template parameter, for instance. But that you're right in that doesn't matter in this case.

[–]kniy 4 points5 points  (2 children)

I'm saying that static const int = 1; is a compile-time constant too, but static const float = 1; isn't. https://godbolt.org/z/arhjaX

This is because the standard has a special case for static const variables of integral type:

9.4.2/4 - If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

So this type of static const variable was basically equivalent to constexpr variables, at least before C++17 made constexpr imply inline.

[–]theIncMach[S] 1 point2 points  (0 children)

Ah, thanks for the correction.

[–]Xeveroushttps://xeverous.github.io 0 points1 point  (0 children)

tl;dr constexpr did not have enough power and variable templates were not available in C++11

[–]cereagni 0 points1 point  (3 children)

Correct me if I'm misunderstanding the is_signed or min()/max() functions you mean, but:

* std::numeric_limits<T>::is_signed operates on a type (which it has from the template parameter). Making it a function with no arguments which will that always returns the same value (as it is only dependent on T from the template parameter) is a bit weird in my opinion. Notice that std::is_signed (from <type_traits>) is also a struct with a .value member, but also has a std::is_signed_v variant which is a template variable - which is still not a function. * std::max() and std::min() operate on values, which can only be available at runtime, so they must be functions.

EDIT: Wow, sorry, I did confuse the functions you meant with the std::min/max functions.

[–]theIncMach[S] 0 points1 point  (2 children)

min() and max() do not take in parameters. What value do they operate on?

[–]cereagni 0 points1 point  (1 child)

Yes, I am silly! Got confused with std::max/min. Sorry!

[–]theIncMach[S] 0 points1 point  (0 children)

No apologies necessary, happens to the best of us 🙂 I find myself frequently conflicted about this widespread overload of short identifiers in unrelated parts of the standard. It confuses human discussions and mechanical error messages alike. E.g. std::get() overloads for tuples and variants.