13

I have a user-defined numeric type S for which I specialized std::numeric_limits<T>.

Although I specialized for S only, my custom max() is also used for cv-qualified S, at least with recent versions of gcc and MSVC.

Is this guaranteed to work, or am I relying on an implementation detail here?

#include <limits>
#include <iostream>

// Some user-defined numeric type,
// for which I'd like to specialize std::numeric_limits<T>.
struct S {};

namespace std
{
    // My specialization, for brevity providing only max()
    template <>
    struct numeric_limits<S>
    {
        static /*constexpr*/ S max()
        {
            std::cout << "got called" << '\n';
            return S();
        }
    };
}

int main()
{
    // Although I specialize for S only, my custom max() is also used for cv-qualified S.
    // Is this guaranteed, or am I relying on an implementation detail here? 
    std::numeric_limits<S>::max();                // Prints 'got called'
    std::numeric_limits<const S>::max();          // Prints 'got called'
    std::numeric_limits<volatile S>::max();       // Prints 'got called'
    std::numeric_limits<const volatile S>::max(); // Prints 'got called'
}
Tom
  • 185
  • 2
  • 2
  • 12
  • 1
    I don't know if this applies to specializations or not but I think the compiler are implementing [this paragraph](https://timsong-cpp.github.io/cppwp/numeric.limits#general-5) for all specializations, not just those built int. – NathanOliver Jun 08 '23 at 19:00
  • 2
    It might be a good idea to add `static_assert(std::numeric_limits<...>::is_specialized);` to make sure a specialization exists. – Evg Jun 08 '23 at 19:10
  • "MSCV" - do you mean "MSVC"? – AJM Jun 15 '23 at 14:26

1 Answers1

16

Yes, it's guaranteed since LWG559 which dealt with this specifically by requiring that

  • The value of each member of a numeric_limits specialization on a cv-qualified T is equal to the value of the same member of numeric_limits<T>.

The proposed (and implemented) resolution was:

  • Add to the synopsis of the <limits> header, immediately below the declaration of the primary template, the following:

    template <class T> class numeric_limits<const T>;
    template <class T> class numeric_limits<volatile T>;
    template <class T> class numeric_limits<const volatile T>;
    

    These are for example implemented in gcc as such:

    template<typename _Tp>
      struct numeric_limits<const _Tp>
      : public numeric_limits<_Tp> { };
    
    template<typename _Tp>
      struct numeric_limits<volatile _Tp>
      : public numeric_limits<_Tp> { };
    
    template<typename _Tp>
      struct numeric_limits<const volatile _Tp>
      : public numeric_limits<_Tp> { };
    

The LWG issue also has an informal note at the bottom:

  • [ Portland: Martin will clarify that user-defined types get cv-specializations automatically. ]

You can see that the addition to the standard works as intended by implementing a similar type, here called foo:

#include <iostream>

// STD
template<class T>
struct foo {
    inline static constexpr bool is_S = false;
};

template <class T> struct foo<const T> : foo<T> {};
template <class T> struct foo<volatile T> : foo<T> {};
template <class T> struct foo<const volatile T> : foo<T> {};

Now add a type, S, and a specialization for foo<S> only:

// User-defined type and foo specialization:

struct S {};

template<>
struct foo<S> {
    inline static constexpr bool is_S = true;
};

and foo<const volatile S> will pick the specialization:

int main() {
    std::cout << foo<const volatile S>::is_S << '\n'; // prints 1
}

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • But that doesn't answer OP's question, does it? That just says that all the CV-qualified versions need to be the same. It doesn't clarify whether the implementer is responsible for defining those variants. For example, the OP could also declare: template<> struct numeric_limits : numeric_limits {}; // and the same for volatile and const volatile The question is: is that necessary or not? – Maks Verver Jun 08 '23 at 19:11
  • 1
    @MaksVerver https://eel.is/c++draft/limits.syn#header:%3climits%3e shows that `` includes a partial specialization for all cv qualified T to enforce this requirement (as suggested by the linked defect issue). So CV qualified specializations shouldn't need to be provided. – François Andrieux Jun 08 '23 at 19:14
  • @MaksVerver Yes, what François' said. The LWG issue I linked to also contains the same tripplet as the proposed solution. I added that to the answer too. – Ted Lyngmo Jun 08 '23 at 19:17
  • @TedLyngmo Thanks for clarifying but I don't think that settles it either. That definition only declares that the four variants exist, but it doesn't define them. Take a look at this: https://gcc.godbolt.org/z/eMozEYqWr The reason OP's code works is that includes a definition like template struct numeric_limits : public numeric_limits – Maks Verver Jun 08 '23 at 20:31
  • @MaksVerver You need to read the LWG issue as a whole. It specifies that _The value of each member of a numeric_limits specialization on a cv-qualified T is equal to the value of the same member of numeric_limits_ and it adds the tripplet to solve that, but as is very common, the _implementation_ of those are up to the library implementors to provide. They only need to make sure that they end up being equal to the non-cv qualified version. And yes, the reason it works is because of the definitions in ``. Your example is lacking the definitions so, of course it will not work. – Ted Lyngmo Jun 08 '23 at 20:36
  • @TedLyngmo I think we're talking past each other. I agree that the standard says that the specialization must be defined and the same for all four cv-qualified versions of a type. But the standard, as far as I can tell, doesn't say how that's accomplished. The question is, those generic definitions in that infer the three cv-qualified versions from the non-cv-qualified version, are they required by standard? Or can a standard-compliant implementation just define e.g. four copies of each specialization and not include the generic definitions? That's the core of the issue. – Maks Verver Jun 08 '23 at 20:43
  • @MaksVerver It doesn't matter. They can be four copies instead of inheriting from `numeric_limits`. They only need to make sure that _"The value of each member of a `numeric_limits` specialization on a cv-qualified `T` is equal to the value of the same member of `numeric_limits`"_. That's what the standard says. – Ted Lyngmo Jun 08 '23 at 20:46
  • 1
    @TedLyngmo I saw you updated your answer to cite an important note: “[ Portland: Martin will clarify that user-defined types get cv-specializations automatically. ]” That's the important part, especially the word *automatically*. Although it's not *technically* part of the C++ standard (yet), I think this settles the question in that the note makes it clear that the intent is to require the library implementations to provide the generic definition, so that user-defined types only need to include an explicit definition for the non-cv quantified version. – Maks Verver Jun 08 '23 at 20:55
  • @MaksVerver Note that this issue has "CD1" status, which means that it was fixed in Committee Draft 1, which means it became official in C++11. – Brian Bi Jun 08 '23 at 23:57
  • 2
    @MaksVerver Note also that according to [[contents]/1](http://eel.is/c++draft/contents#1), the presence of e.g. `template class numeric_limits;` in the synopsis implies that the implementation actually provides the full definition for that particular partial specialization. The programmer doesn't provide the definition. – Brian Bi Jun 09 '23 at 00:03