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'
}
Yes, it's guaranteed since LWG559 which dealt with this specifically by requiring that
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:
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
}