Reputation: 20396
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
Reputation: 96579
Specializing templates from <type_traits>
(other than std::common_type
) appears to not be allowed:
None of the templates defined in
<type_traits>
may be specialized for a program-defined type, except forstd::common_type
.
The revelant part of the standard is: (courtesy of @NathanOliver)
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