Deprecated std::is_literal_type in C++17 - c++

According to cppreference, the trait std::is_literal_type is deprecated in C++17. The question is why and what is the preferred replacement for the future to check whether a type is a literal type.

As stated in P0174:
The is_literal type trait offers negligible value to generic code, as what is really needed is the ability to know that a specific construction would produce constant initialization. The core term of a literal type having at least one constexpr constructor is too weak to be used meaningfully.
Basically, what it's saying is that there's no code you can guard with is_literal_type_v and have that be sufficient to ensure that your code actually is constexpr. This isn't good enough:
template<typename T>
std::enable_if_t<std::is_literal_type_v<T>, void> SomeFunc()
{
constexpr T t{};
}
There's no guarantee that this is legal. Even if you guard it with is_default_constructible<T> that doesn't mean that it's constexpr default constructible.
What you would need is an is_constexpr_constructible trait. Which does not as of yet exist.
However, the (already implemented) trait does no harm, and allows compile-time introspection for which core-language type-categories a given template parameter might satisfy. Until the Core Working Group retire the notion of a literal type, the corresponding library trait should be preserved.
The next step towards removal (after deprecation) would be to write a paper proposing to remove the term from the core language while deprecating/removing the type trait.
So the plan is to eventually get rid of the whole definition of "literal types", replacing it with something more fine-grained.

Related

C++20: Validate Template Bodies Against Concepts

C++20 introduces concepts, which allows us to specify in the declaration of a template that the template parameters must provide certain capabilities. If a template is instantiated with a type that does not satisfy the constraints, compilation will fail at instantiation instead of while compiling the template's body and noticing an invalid expression after substitution.
This is great, but it begs the question: is there a way to have the compiler look at the template body, before instantiation (i.e. looking at it as a template and not a particular instantiation of a template), and check that all the expressions involving template parameters are guaranteed by the constraints to exist?
Example:
template<typename T>
concept Fooer = requires(T t)
{
{ t.foo() };
};
template<Fooer F>
void callFoo(F&& fooer)
{
fooer.foo();
}
The concept prevents me from instantiating callFoo with a type that doesn't support the expression that's inside the template body. However, if I change the function to this:
template<Fooer F>
void callFoo(F&& fooer)
{
fooer.foo();
fooer.bar();
}
This will fail if I instantiate callFoo with a type that defines foo (and therefore satisfies the constraints) but not bar. In principal, the concept should enable the compiler to look at this template and reject it before instantiation because it includes the expression fooer.bar(), which is not guaranteed by the constraint to exist.
I assume there's probably backward compatibility issues with doing this, although if this validation is only done with parameters that are constrained (not just typename/class/etc. parameters), it should only affect new code.
This could be very useful because the resulting errors could be used to guide the design of constraints. Write the template implementation, compile (with no instantiations yet), then on each error, add whatever requirement is needed to the constraint. Or, in the opposite direction, when hitting an error, adjust the implementation to use only what the constraints provide.
Do any compilers support an option to enable this type of validation, or is there a plan to add this at any point? Is it part of the specification for concepts to do this validation, now or in the future?
Do any compilers support an option to enable this type of validation, or is there a plan to add this at any point? Is it part of the specification for concepts to do this validation, now or in the future?
No, no, and no.
The feature you're looking for is called definition checking. That is, the compiler checks the definition of the template at the point of its definition based on the provided concepts, and issues errors if anything doesn't validate. This is how, for instance, Rust Traits, Swift Protocols, and Haskell Typeclasses work.
But C++ concepts don't work like that, and it seems completely infeasible to ever add support for such a thing given that C++ concepts can be arbitrary expressions rather than function signatures (as they are in other languages).
The best you can do is thoroughly unit test your templates with aggressively exotic types that meet your requirements as minimally as possible (the term here is archetype) and hope for the best.
TL;DR: no.
The design for the original C++11 concepts included validation. But when that was abandoned, the new version was designed to be much more narrow in scope. The new design was originally built on constexpr boolean conditions. The eventual requires expression was added to make these boolean checks easier to write and to bring some sanity to relationships between concepts.
But the fundamentals of the design of C++20 concepts makes it basically impossible to do full validation. Even if a concept is built entirely out of atomic requires expressions, there isn't a way to really tell if an expression is being used exactly in the code the way it is in the requires expression.
For example, consider this concept:
template<typename T, typename U>
concept func_to_u = requires(T const t)
{
{t.func()} -> std::convertible_to<U>;
};
Now, let's imagine the following template:
template<typename T, typename U> requires func_to_u<T, U>
void foo(T const &t)
{
std::optional<U> u(std::in_place, t.func());
}
If you look at std::optional, you find that the in_place_t constructor doesn't take a U. So... is this a legitimate use of that concept? After all, the concept says that code guarded by this concept will call func() and will convert the result to a U. But this template does not do this.
It instead takes the return type, instantiates a template that is not guarded by func_to_u, and that template does whatever it wants. Now, it turns out that this template does perform a conversion operation to U.
So on the one hand, it's clear that our code does conform to the intent of func_to_u. But that is only because it happened to pass the result to some other function that conformed to the func_to_u concept. But that template had no idea it was subject to the limitations of convertible_to<U>.
So... how is the compiler supposed to detect whether this is OK? The trigger condition for failure would be somewhere in optional's constructor. But that constructor is not subject to the concept; it's our outer code that is subject to the concept. So the compiler would basically have to unwind every template your code uses and apply the concept to it. Only it wouldn't even be applying the whole concept; it would just be applying the convertible_to<U> part.
The complexity of doing that quickly spirals out of control.

How can one implement is_aggregate without compiler magic? [duplicate]

C++11 provides standard <type_traits>.
Which of them are impossible to implement without compiler hooks?
Note 1: by compiler hook I mean any non-standard language feature such as __is_builtin....
Note 2: a lot of them can be implemented without hooks (see chapter 2 of C++ Template Metaprogramming and/or chapter 2 of Modern C++ Design).
Note 3: spraff answer in this previous question cites N2984 where some type traits contain the following note: is believed to require compiler support (thanks sehe).
I have written up a complete answer here — it's a work in progress, so I'm giving the authoritative hyperlink even though I'm cutting-and-pasting the text into this answer.
Also see libc++'s documentation on Type traits intrinsic design.
is_union
is_union queries an attribute of the class that isn't exposed through any other means;
in C++, anything you can do with a class or struct, you can also do with a union. This
includes inheriting and taking member pointers.
is_aggregate, is_literal_type, is_pod, is_standard_layout, has_virtual_destructor
These traits query attributes of the class that aren't exposed through any other means.
Essentially, a struct or class is a "black box"; the C++ language gives us no way to
crack it open and examine its data members to find out if they're all POD types, or if
any of them are private, or if the class has any constexpr constructors (the key
requirement for is_literal_type).
is_abstract
is_abstract is an interesting case. The defining characteristic of an abstract
class type is that you cannot get a value of that type; so for example it is
ill-formed to define a function whose parameter or return type is abstract, and
it is ill-formed to create an array type whose element type is abstract.
(Oddly, if T is abstract, then SFINAE will apply to T[] but not to T(). That
is, it is acceptable to create the type of a function with an abstract return type;
it is ill-formed to define an entity of such a function type.)
So we can get very close to a correct implementation of is_abstract using
this SFINAE approach:
template<class T, class> struct is_abstract_impl : true_type {};
template<class T> struct is_abstract_impl<T, void_t<T[]>> : false_type {};
template<class T> struct is_abstract : is_abstract_impl<remove_cv_t<T>, void> {};
However, there is a flaw! If T is itself a template class, such as vector<T>
or basic_ostream<char>, then merely forming the type T[] is acceptable; in
an unevaluated context this will not cause the compiler to go instantiate the
body of T, and therefore the compiler will not detect the ill-formedness of
the array type T[]. So the SFINAE will not happen in that case, and we'll
give the wrong answer for is_abstract<basic_ostream<char>>.
This quirk of template instantiation in unevaluated contexts is the sole reason
that modern compilers provide __is_abstract(T).
is_final
is_final queries an attribute of the class that isn't exposed through any other means.
Specifically, the base-specifier-list of a derived class is not a SFINAE context; we can't
exploit enable_if_t to ask "can I create a class derived from T?" because if we
cannot create such a class, it'll be a hard error.
is_empty
is_empty is an interesting case. We can't just ask whether sizeof (T) == 0 because
in C++ no type is ever allowed to have size 0; even an empty class has sizeof (T) == 1.
"Emptiness" is important enough to merit a type trait, though, because of the Empty Base
Optimization: all sufficiently modern compilers will lay out the two classes
struct Derived : public T { int x; };
struct Underived { int x; };
identically; that is, they will not lay out any space in Derived for the empty
T subobject. This suggests a way we could test for "emptiness" in C++03, at least
on all sufficiently modern compilers: just define the two classes above and ask
whether sizeof (Derived) == sizeof (Underived). Unfortunately, as of C++11, this
trick no longer works, because T might be final, and the "final-ness" of a class
type is not exposed by any other means! So compiler vendors who implement final
must also expose something like __is_empty(T) for the benefit of the standard library.
is_enum
is_enum is another interesting case. Technically, we could implement this type trait
by the observation that if our type T is not a fundamental type, an array type,
a pointer type, a reference type, a member pointer, a class or union, or a function
type, then by process of elimination it must be an enum type. However, this deductive
reasoning breaks down if the compiler happens to support any other types not falling
into the above categories. For this reason, modern compilers expose __is_enum(T).
A common example of a supported type not falling into any of the above categories
would be __int128_t. libc++ actually detects the presence of __int128_t and includes
it in the category of "integral types" (which makes it a "fundamental type" in the above
categorization), but our simple implementation does not.
Another example would be vector int, on compilers supporting Altivec vector extensions;
this type is more obviously "not integral" but also "not anything else either", and most
certainly not an enum type!
is_trivially_constructible, is_trivially_assignable
The triviality of construction, assignment, and destruction are all attributes of the
class that aren't exposed through any other means. Notice that with this foundation
we don't need any additional magic to query the triviality of default construction,
copy construction, move assignment, and so on. Instead,
is_trivially_copy_constructible<T> is implemented in terms of
is_trivially_constructible<T, const T&>, and so on.
is_trivially_destructible
For historical reasons, the name of this compiler builtin is not __is_trivially_destructible(T)
but rather __has_trivial_destructor(T). Furthermore, it turns out that the builtin
evaluates to true even for a class type with a deleted destructor! So we first need
to check that the type is destructible; and then, if it is, we can ask the magic builtin
whether that destructor is indeed trivial.
underlying_type
The underlying type of an enum isn't exposed through any other means. You can get close
by taking sizeof(T) and comparing it to the sizes of all known types, and by asking
for the signedness of the underlying type via T(-1) < T(0); but that approach still
cannot distinguish between underlying types int and long on platforms where those
types have the same width (nor between long and long long on platforms where those
types have the same width).
Per lastest boost documentation which is also a bit old but I think it's valid still
Support for Compiler Intrinsics
There are some traits that can not be implemented within the current C++ language: to make these traits "just work" with user defined types, some kind of additional help from the compiler is required. Currently (April 2008) Visual C++ 8 and 9, GNU GCC 4.3 and MWCW 9 provide at least some of the the necessary intrinsics, and other compilers will no doubt follow in due course.
The Following traits classes always need compiler support to do the right thing for all types (but all have safe fallback positions if this support is unavailable):
is_union
is_pod
has_trivial_constructor
has_trivial_copy
has_trivial_move_constructor
has_trivial_assign
has_trivial_move_assign
has_trivial_destructor
has_nothrow_constructor
has_nothrow_copy
has_nothrow_assign
has_virtual_destructor
The following traits classes can't be portably implemented in the C++ language, although in practice, the implementations do in fact do the right thing on all the compilers we know about:
is_empty
is_polymorphic
The following traits classes are dependent on one or more of the above:
is_class

Floating-point types as template parameter in C++20

According to cppreference C++20 now supports floating-point parameters in templates.
I am, however, unable to find any compiler support information on that site as well as on others.
Current gcc trunk just does it, the others are negative.
I would just like to know if this is a very low-priortity feature and/or when to expect it to become commonly supported.
The only related thing I can find is:
P0732R2 Class types in non-type template parameters. Kudos if anyone could briefly explain that instead.
It seems that the real question that can be answered here is about the history of this feature, so that whatever compiler support can be understood in context.
Limitations on non-type template parameter types
People have been wanting class-type non-type template parameters for a long time. The answers there are somewhat lacking; what really makes support for such template parameters (really, of non-trivial user-defined types) complicated is their unknown notion of identity: given
struct A {/*...*/};
template<A> struct X {};
constexpr A f() {/*...*/}
constexpr A g() {/*...*/}
X<f()> xf;
X<g()> &xg=xf; // OK?
how do we decide whether X<f()> and X<g()> are the same type? For integers, the answer seems intuitively obvious, but a class type might be something like std::vector<int>, in which case we might have
// C++23, if that
using A=std::vector<int>;
constexpr A f() {return {1,2,3};}
constexpr A g() {
A ret={1,2,3};
ret.reserve(1000);
return ret;
}
and it's not clear what to make of the fact that both objects contain the same values (and hence compare equal with ==) despite having very different behavior (e.g., for iterator invalidation).
P0732 Class types in non-type template parameters
It's true that this paper first added support for class-type non-type template parameters, in terms of the new <=> operator. The logic was that classes that defaulted that operator were "transparent to comparisons" (the term used was "strong structural equality") and so programmers and compilers could agree on a definition of identity.
P1185 <=> != ==
Later it was realized that == should be separately defaultable for performance reasons (e.g., it allows an early exit for comparing strings of different lengths), and the definition of strong structural equality was rewritten in terms of that operator (which comes for free along with a defaulted <=>). This doesn't affect this story, but the trail is incomplete without it.
P1714 NTTP are incomplete without float, double, and long double!
It was discovered that class-type NTTPs and the unrelated feature of constexpr std::bit_cast allowed a floating-point value to be smuggled into a template argument inside a type like std::array<std::byte,sizeof(float)>. The semantics that would result from such a trick would be that every representation of a float would be a different template argument, despite the fact that -0.0==0.0 and (given float nan=std::numeric_limits<float>::quiet_NaN();) nan!=nan. It was therefore proposed that floating-point values be allowed directly as template arguments, with those semantics, to avoid encouraging widespread adoption of such a hacky workaround.
At the time, there was a lot of confusion around the idea that (given template<auto> int vt;) x==y might differ from &vt<x>==&vt<y>), and the proposal was rejected as needing more analysis than could be afforded for C++20.
P1907R0 Inconsistencies with non-type template parameters
It turns out that == has a lot of problems in this area. Even enumerations (which have always been allowed as template parameter types) can overload ==, and using them as template arguments simply ignores that overload entirely. (This is more or less necessary: such an operator might be defined in some translation units and not others, or might be defined differently, or have internal linkage, etc.) Moreover, what an implementation needs to do with a template argument is canonicalize it: to compare one template argument (in, say, a call) to another (in, say, an explicit specialization) would require that the latter had somehow already been identified in terms of the former while somehow allowing the possibility that they might differ.
This notion of identity already differs from == for other types as well. Even P0732 recognized that references (which can also be the type of template parameters) aren't compared with ==, since of course x==y does not imply that &x==&y. Less widely appreciated was that pointers-to-members also violate this correspondence: because of their different behavior in constant evaluation, pointers to different members of a union are distinct as template arguments despite comparing ==, and pointers-to-members that have been cast to point into a base class have similar behavior (although their comparison is unspecified and hence disallowed as a direct component of constant evaluation).
In fact, in November 2019 GCC had already implemented basic support for class-type NTTPs without requiring any comparison operator.
P1837 Remove NTTPs of class type from C++20
These incongruities were so numerous that it had already been proposed that the entire feature be postponed until C++23. In the face of so many problems in so popular a feature, a small group was commissioned to specify the significant changes necessary to save it.
P1907R1 (structural types)
These stories about template arguments of class type and of floating-point type reconverge in the revision of P1907R0 which retained its name but replaced its body with a solution to National Body comments that had also been filed on the same subject. The (new) idea was to recognize that comparisons had never really been germane, and that the only consistent model for template argument identity was that two arguments were different if there was any means of distinguishing them during constant evaluation (which has the aforementioned power to distinguish pointers-to-members, etc.). After all, if two template arguments produce the same specialization, that specialization must have one behavior, and it must be the same as would be obtained from using either of the arguments directly.
While it would be desirable to support a wide range of class types, the only ones that could be reliably supported by what was a new feature introduced (or rather rewritten) at almost the last possible moment for C++20 were those where every value that could be distinguished by the implementation could be distinguished by its clients—hence, only those that have all public members (that recursively have this property). The restrictions on such structural types are not quite as strong as those on an aggregate, since any construction process is permissible so long as it is constexpr. It also has plausible extensions for future language versions to support more class types, perhaps even std::vector<T>—again, by canonicalization (or serialization) rather than by comparison (which cannot support such extensions).
The general solution
This newfound understanding has no relationship to anything else in C++20; class-type NTTPs using this model could have been part of C++11 (which introduced constant expressions of class type). Support was immediately extended to unions, but the logic is not limited to classes at all; it also established that the longstanding prohibitions of template arguments that were pointers to subobjects or that had floating-point type had also been motivated by confusion about == and were unnecessary. (While this doesn't allow string literals to be template arguments for technical reasons, it does allow const char* template arguments that point to the first character of static character arrays.)
In other words, the forces that motivated P1714 were finally recognized as inevitable mathematical consequences of the fundamental behavior of templates and floating-point template arguments became part of C++20 after all. However, neither floating-point nor class-type NTTPs were actually specified for C++20 by their original proposals, complicating "compiler support" documentation.

Why is the type of boost::hana::tuple_c implementation-defined?

The Boost.Hana documentation for tuple_c states:
Also note that the type of the objects returned by tuple_c and an
equivalent call to make<tuple_tag> may differ.
followed by the following snippet:
BOOST_HANA_CONSTANT_CHECK(
hana::to_tuple(hana::tuple_c<int, 0, 1, 2>)
==
hana::make_tuple(hana::int_c<0>, hana::int_c<1>, hana::int_c<2>)
);
However, the actual implementation for tuple_c simply has:
#ifdef BOOST_HANA_DOXYGEN_INVOKED
template <typename T, T ...v>
constexpr implementation_defined tuple_c{};
#else
template <typename T, T ...v>
constexpr hana::tuple<hana::integral_constant<T, v>...> tuple_c{};
#endif
and, indeed, the code snippet works just fine without the to_tuple wrapper:
BOOST_HANA_CONSTANT_CHECK(
hana::tuple_c<int, 0, 1, 2>
==
hana::make_tuple(hana::int_c<0>, hana::int_c<1>, hana::int_c<2>)
);
Question: why is the actual type of tuple_c implementation defined? Isn't the to_tuple wrapper superfluous?
The phrase "implementation defined" does not describe an implementation. It explicitly states that an implementation choice is left undocumented on purpose, for one reason or another. Of course it is implemented somehow. The users should not rely on any particular implementation but use only documented APIs.
Leaving an implementation choice undocumented is a sensible default unless there's a specific reason to document it. This is true even if there's only one obvious choice today, because tomorrow things may change.
Actually, the documentation has this covered in a FAQ:
Why leave some container's representation implementation-defined?
First, it gives much more wiggle room for the implementation to
perform compile-time and runtime optimizations by using clever
representations for specific containers. For example, a tuple
containing homogeneous objects of type T could be implemented as an
array of type T instead, which is more efficient at compile-time.
Secondly, and most importantly, it turns out that knowing the type of
a heterogeneous container is not as useful as you would think. Indeed,
in the context of heterogeneous programming, the type of the object
returned by a computation is usually part of the computation too. In
other words, there is no way to know the type of the object returned
by an algorithm without actually performing the algorithm.
Not speaking with authority, but I would say that wrapping the tuple_c with to_tuple is in fact superfluous. The documentation states that the result is functionally equivalent to make_tuple except that the type is not guaranteed to be the same.
One possible optimization would be returning something like this:
template <auto ...i>
struct tuple_c_t { };
To be sure I made a pull request to see if we can get the superfluous conversion removed from the example.
https://github.com/boostorg/hana/pull/394
UPDATE: It was confirmed by the author of Boost.Hana that the conversion is unnecessary and the example was updated to reflect that.

Which <type_traits> cannot be implemented without compiler hooks?

C++11 provides standard <type_traits>.
Which of them are impossible to implement without compiler hooks?
Note 1: by compiler hook I mean any non-standard language feature such as __is_builtin....
Note 2: a lot of them can be implemented without hooks (see chapter 2 of C++ Template Metaprogramming and/or chapter 2 of Modern C++ Design).
Note 3: spraff answer in this previous question cites N2984 where some type traits contain the following note: is believed to require compiler support (thanks sehe).
I have written up a complete answer here — it's a work in progress, so I'm giving the authoritative hyperlink even though I'm cutting-and-pasting the text into this answer.
Also see libc++'s documentation on Type traits intrinsic design.
is_union
is_union queries an attribute of the class that isn't exposed through any other means;
in C++, anything you can do with a class or struct, you can also do with a union. This
includes inheriting and taking member pointers.
is_aggregate, is_literal_type, is_pod, is_standard_layout, has_virtual_destructor
These traits query attributes of the class that aren't exposed through any other means.
Essentially, a struct or class is a "black box"; the C++ language gives us no way to
crack it open and examine its data members to find out if they're all POD types, or if
any of them are private, or if the class has any constexpr constructors (the key
requirement for is_literal_type).
is_abstract
is_abstract is an interesting case. The defining characteristic of an abstract
class type is that you cannot get a value of that type; so for example it is
ill-formed to define a function whose parameter or return type is abstract, and
it is ill-formed to create an array type whose element type is abstract.
(Oddly, if T is abstract, then SFINAE will apply to T[] but not to T(). That
is, it is acceptable to create the type of a function with an abstract return type;
it is ill-formed to define an entity of such a function type.)
So we can get very close to a correct implementation of is_abstract using
this SFINAE approach:
template<class T, class> struct is_abstract_impl : true_type {};
template<class T> struct is_abstract_impl<T, void_t<T[]>> : false_type {};
template<class T> struct is_abstract : is_abstract_impl<remove_cv_t<T>, void> {};
However, there is a flaw! If T is itself a template class, such as vector<T>
or basic_ostream<char>, then merely forming the type T[] is acceptable; in
an unevaluated context this will not cause the compiler to go instantiate the
body of T, and therefore the compiler will not detect the ill-formedness of
the array type T[]. So the SFINAE will not happen in that case, and we'll
give the wrong answer for is_abstract<basic_ostream<char>>.
This quirk of template instantiation in unevaluated contexts is the sole reason
that modern compilers provide __is_abstract(T).
is_final
is_final queries an attribute of the class that isn't exposed through any other means.
Specifically, the base-specifier-list of a derived class is not a SFINAE context; we can't
exploit enable_if_t to ask "can I create a class derived from T?" because if we
cannot create such a class, it'll be a hard error.
is_empty
is_empty is an interesting case. We can't just ask whether sizeof (T) == 0 because
in C++ no type is ever allowed to have size 0; even an empty class has sizeof (T) == 1.
"Emptiness" is important enough to merit a type trait, though, because of the Empty Base
Optimization: all sufficiently modern compilers will lay out the two classes
struct Derived : public T { int x; };
struct Underived { int x; };
identically; that is, they will not lay out any space in Derived for the empty
T subobject. This suggests a way we could test for "emptiness" in C++03, at least
on all sufficiently modern compilers: just define the two classes above and ask
whether sizeof (Derived) == sizeof (Underived). Unfortunately, as of C++11, this
trick no longer works, because T might be final, and the "final-ness" of a class
type is not exposed by any other means! So compiler vendors who implement final
must also expose something like __is_empty(T) for the benefit of the standard library.
is_enum
is_enum is another interesting case. Technically, we could implement this type trait
by the observation that if our type T is not a fundamental type, an array type,
a pointer type, a reference type, a member pointer, a class or union, or a function
type, then by process of elimination it must be an enum type. However, this deductive
reasoning breaks down if the compiler happens to support any other types not falling
into the above categories. For this reason, modern compilers expose __is_enum(T).
A common example of a supported type not falling into any of the above categories
would be __int128_t. libc++ actually detects the presence of __int128_t and includes
it in the category of "integral types" (which makes it a "fundamental type" in the above
categorization), but our simple implementation does not.
Another example would be vector int, on compilers supporting Altivec vector extensions;
this type is more obviously "not integral" but also "not anything else either", and most
certainly not an enum type!
is_trivially_constructible, is_trivially_assignable
The triviality of construction, assignment, and destruction are all attributes of the
class that aren't exposed through any other means. Notice that with this foundation
we don't need any additional magic to query the triviality of default construction,
copy construction, move assignment, and so on. Instead,
is_trivially_copy_constructible<T> is implemented in terms of
is_trivially_constructible<T, const T&>, and so on.
is_trivially_destructible
For historical reasons, the name of this compiler builtin is not __is_trivially_destructible(T)
but rather __has_trivial_destructor(T). Furthermore, it turns out that the builtin
evaluates to true even for a class type with a deleted destructor! So we first need
to check that the type is destructible; and then, if it is, we can ask the magic builtin
whether that destructor is indeed trivial.
underlying_type
The underlying type of an enum isn't exposed through any other means. You can get close
by taking sizeof(T) and comparing it to the sizes of all known types, and by asking
for the signedness of the underlying type via T(-1) < T(0); but that approach still
cannot distinguish between underlying types int and long on platforms where those
types have the same width (nor between long and long long on platforms where those
types have the same width).
Per lastest boost documentation which is also a bit old but I think it's valid still
Support for Compiler Intrinsics
There are some traits that can not be implemented within the current C++ language: to make these traits "just work" with user defined types, some kind of additional help from the compiler is required. Currently (April 2008) Visual C++ 8 and 9, GNU GCC 4.3 and MWCW 9 provide at least some of the the necessary intrinsics, and other compilers will no doubt follow in due course.
The Following traits classes always need compiler support to do the right thing for all types (but all have safe fallback positions if this support is unavailable):
is_union
is_pod
has_trivial_constructor
has_trivial_copy
has_trivial_move_constructor
has_trivial_assign
has_trivial_move_assign
has_trivial_destructor
has_nothrow_constructor
has_nothrow_copy
has_nothrow_assign
has_virtual_destructor
The following traits classes can't be portably implemented in the C++ language, although in practice, the implementations do in fact do the right thing on all the compilers we know about:
is_empty
is_polymorphic
The following traits classes are dependent on one or more of the above:
is_class