Xirema
Xirema

Reputation: 20396

Is Microsoft Visual Studio improperly defining the values of Type Traits?

As part of some Geometric functions I'm implementing for a project, I figured it would be a good idea to help constrain the types that could be fed into the library to only Geometric Types. So I wrote the following struct to aid with that:

template<typename T>
struct Geometric {
    static_assert(std::is_arithmetic_v<T>, "T Must be Arithmetic!");
    static_assert(std::is_same_v<T, std::decay_t<T>>, "Must be raw type!");
};

//...

template<typename T>
struct point2 : Geometic<T> {
    //...
};

Then, because my library needs to make use of a custom type that obeys all the common conventions of arithmetic types (at least as far as this project is concerned) I figured an easy solution to integrate this type into the library was to simply add an entry for std::is_arithmetic for that type:

using rational_t = boost::multiprecision::cpp_rational;

template<>
struct std::is_arithmetic<rational_t> : std::true_type {};

using rational_point = point2<rational_t>;

However, this code consistently produced the "T must be arithmetic!" error message at compile-time. So I did a little digging into Microsoft's implementation of these type traits and found something that I wasn't expecting: the derivation of std::is_arithmetic_v<T> comes directly from other constants, not from its associated struct.

//MSVC2019 Code found in 'xtr1common', line# 197
// STRUCT TEMPLATE is_arithmetic
template <class _Ty>
_INLINE_VAR constexpr bool is_arithmetic_v = // determine whether _Ty is an arithmetic type
    is_integral_v<_Ty> || is_floating_point_v<_Ty>;

template <class _Ty>
struct is_arithmetic : bool_constant<is_arithmetic_v<_Ty>> {};

This, of course, stymied my code, because my specialization of is_arithmetic<rational_t> was having absolutely no effect on the definition of is_arithmetic_v<rational_t>.

It specifically stymied me because I was expecting the specialization to work in reverse, deriving is_arithmetic_v from is_arithmetic, as it's detailed on CPPReference's page for this type trait:

Helper variable template

template< class T >
inline constexpr bool is_arithmetic_v = is_arithmetic<T>::value;

Possible implementation

template< class T >
struct is_arithmetic : std::integral_constant<bool,
                                              std::is_integral<T>::value ||
                                              std::is_floating_point<T>::value> {};

So is MSVC's definition of this trait (and many other traits, which seem to follow similar patterns) legal according to the standard, or is this a mistake in the implementation? If I need to specialize these kinds of constants in the future, what's the ideomatic way to do so?

Upvotes: 1

Views: 183

Answers (1)

HolyBlackCat
HolyBlackCat

Reputation: 96579

Specializing templates from <type_traits> (other than std::common_type) appears to not be allowed:

cppreference:

None of the templates defined in <type_traits> may be specialized for a program-defined type, except for std::common_type.

The revelant part of the standard is: (courtesy of @NathanOliver)

[meta.rqmts]/4:

Unless otherwise specified, the behavior of a program that adds specializations for any of the templates specified in this subclause [meta] is undefined.

So the implementation used by MSVC is conforming.

The classical workaround is to make your own trait (based on the standard one) and specialize it instead.

template <typename T>
struct IsArithmetic : std::is_arithmetic<T> {};

Upvotes: 4

Related Questions