Member traits is a Circle extension that makes using numeric_limits
and type_traits
fun and easy. These constants and types are accessed like non-static data members on a type.
#include <limits>
#include <type_traits>
#include <array>
#include <list>
#include <iostream>
struct base_t { virtual void func() = 0; };
struct derived_t final : base_t { virtual void func() override { } };
enum enum_t : short { };
int main() {
// Access all numeric_traits.
std::cout<< float.max << ", "<< float.epsilon<< "\n";
std::cout<< double.max << ", "<< double.epsilon<< "\n";
std::cout<< (long double).max<< ", "<< (long double).epsilon<< "\n";
// Access type_traits.
static_assert(void.is_void);
static_assert(std::array<float, 5>.is_aggregate);
static_assert(!std::list<char>.is_aggregate);
static_assert((float[][2][3]).is_unbounded_array);
static_assert((long(std::list<char>::*)).is_member_object_pointer);
static_assert(derived_t.is_final);
static_assert(base_t.is_polymorphic);
static_assert(enum_t.is_enum);
// Mutate types using type_traits.
static_assert(std::is_same_v<
const int*.remove_pointer.make_unsigned,
const unsigned int
>);
static_assert(std::is_same_v<
enum_t.underlying_type.add_rvalue_reference,
short&&
>);
static_assert(std::is_same_v<
char(.decay)[3], // This is how to write (char[3]).decay.
char*
>);
}
$ circle -std=c++20 traits1.cxx && ./traits1
3.40282e+38, 1.19209e-07
1.79769e+308, 2.22045e-16
1.18973e+4932, 1.0842e-19
There are three kinds of traits supported with this extension:
-
Value members of
std::numeric_limits
. These are only supported for types that have a partial or explicit specialization of thestd::numeric_limits
class template. Attempting to accessa numeric limit on a type that doesn't have a specialization (by default, any non-builtin or non-arithmetic type) raises a SFINAE failure. Thehas_numeric_limits
trait indicates if the type on the left-hand side has a specialization.has_numeric_limits
- a special trait that indicates if the numeric limits are available.is_signed
is_integer
is_exact
has_infinity
has_quiet_NaN
has_signaling_NaN
has_denorm
has_denorm_loss
round_style
is_iec559
is_bounded
is_modulo
digits
digits10
max_digits10
radix
min_exponent
min_exponent10
max_exponent
max_exponent10
traps
tinyness_before
min
lowest
max
epsilon
round_error
infinity
quiet_NaN
signaling_NaN
denorm_min
-
Value members of
std::type_traits
. These are supported for all C++ types. Some of these traits require C++20 or even C++23 headers.is_void
is_null_pointer
is_integral
is_floating_point
is_array
is_enum
is_union
is_class
is_function
is_pointer
is_lvalue_reference
is_rvalue_reference
is_member_object_pointer
is_member_function_pointer
is_fundamental
is_arithmetic
is_scalar
is_object
is_compound
is_reference
is_member_pointer
is_const
is_volatile
is_trivial
is_trivially_copyable
is_standard_layout
has_unique_object_representations
is_empty
is_polymorphic
is_abstract
is_final
is_aggregate
is_unsigned
is_bounded_array
is_unbounded_array
is_scoped_enum
rank
-
Type aliases of
std::type_traits
. These are conditionally supported. Since types are parsed differently from values, the.member-trait-name
syntax applies like a ptr-operator. It has left-to-right associativity. Chain them together to perform complex type transformations. Note that if you want to apply a type alias member trait to a declarator that has a noptr-operator, you'll need to use C++'s quite obscure spiral rule for declarations.char(.decay)[3]
decays the typechar[3]
. We can't simply writechar[3].decay
, because that is ungrammatical in C++.