Section: 16.4.6 [conforming], 20.2.9 [allocator.traits], 20.5.1 [allocator.adaptor.syn] Status: C++14 Submitter: Daniel Krügler Opened: 2011-11-30 Last modified: 2016-01-28
Priority: 1
View all other issues in [conforming].
View all issues with C++14 status.
Discussion:
It is a very established technique for implementations to derive internally from user-defined class types that are used to customize some library component, e.g. deleters and allocators are typical candidates. The advantage of this approach is to possibly take advantage of the empty-base-class optimization (EBCO).
Whether or whether not libraries did take advantage of such a detail didn't much matter in C++03. Even though there did exist a portable idiom to prevent that a class type could be derived from, this idiom has never reached great popularity: The technique required to introduce a virtual base class and it did not really prevent the derivation, but only any construction of such a type. Further, such types are not empty as defined by thestd::is_empty
trait, so
could easily be detected by implementations from TR1 on.
With the new C++11 feature of final classes and final member functions it is now very easy to define an empty,
but not derivable from class type. From the point of the user it is quite natural to use this feature for
types that he or she did not foresee to be derivable from.
On the other hand, most library implementations (including third-party libraries) often take advantage of EBCO
applied to user-defined types used to instantiate library templates internally. As the time of submitting this
issue the following program failed to compile on all tested library implementations:
#include <memory>
struct Noop final {
template<class Ptr>
void operator()(Ptr) const {}
};
std::unique_ptr<int, Noop> up;
In addition, many std::tuple
implementations with empty, final classes as element types failed as well,
due to a popular inheritance-based implementation technique. EBCO has also a long tradition to be
used in library containers to efficiently store potentially stateless, empty allocators.
__is_final
or __is_derivable
to make EBCO possible in the current form but excluding non-derivable class types. As of this writing this
seems to happen already. Problem is that without a std::is_derivable
trait, third-party libraries
have no portable means to do the same thing as standard library implementations. This should be a good
reason to make such a trait public available soon, but seems not essential to have now. Further, this issue
should also be considered as a chance to recognice that EBCO has always been a very special corner case
(There exist parallels to the previously existing odd core language rule that did make the interplay
between std::auto_ptr
and std::auto_ptr_ref
possible) and that it would be better to
provide explicit means for space-efficient storage, not necessarily restricted to inheritance relations,
e.g. by marking data members with a special attribute.
At least two descriptions in the current standard should be fixed now for better clarification:
As mentioned by Ganesh, 20.2.9 [allocator.traits] p1 currently contains a (non-normative) note "Thus, it is always possible to create a derived class from an allocator." which should be removed.
As pointed out by Howard, the specification of scoped_allocator_adaptor
as of
20.5.1 [allocator.adaptor.syn] already requires derivation from OuterAlloc
, but
only implies indirectly the same for the inner allocators due to the exposition-only
description of member inner
. This indirect implication should be normatively required for
all participating allocators.
[2012, Kona]
What we really need is a type trait to indicate if a type can be derived from. Howard reports Clang and libc++ have had success with this approach.
Howard to provide wording, and AJM to alert Core that we may be wanting to add a new trait that requires compiler support.
[2014-02, Issaquah: Howard and Daniel comment and provide wording]
Several existing C++11 compilers do already provide an internal __is_final
intrinsic (e.g. clang and gcc) and therefore
we believe that this is evidence enough that this feature is implementable today.
is_final
query should result in a true outcome
if and only if the current existing language definition holds that a complete class type (either union or non-union)
has been marked with the class-virt-specifier final
— nothing more.
The following guidelines lead to the design decision and the wording choice given below:
It has been expressed several times that a high-level trait such as "is_derivable
" would be preferred and
would be more useful for non-experts. One problem with that request is that it is astonishingly hard to find a common denominator
for what the precise definition of this trait should be, especially regarding corner-cases. Another example of getting very
differing points of view is to ask a bunch of C++ experts what the best definition of the is_empty
trait
should be (which can be considered as a kind of higher-level trait).
Once we have a fundamental trait like is_final
available, we can easily define higher-level traits in the future
on top of this by a proper logical combination of the low-level traits.
A critical question is whether providing such a low-level compile-time introspection might be considered as disadvantageous,
because it could constrain the freedom of existing implementations even further and whether a high-level trait would solve
this dilemma. We assert that since C++11 the static introspection capabilities are already very large and we believe that
making the presence or absence of the final
keyword testable does not make the current situation worse.
Below code example demonstrates the intention and the implementability of this feature:
#include <type_traits> namespace std { template <class T> struct is_final : public integral_constant<bool, __is_final(T)> {}; } // std // test it union FinalUnion final { }; union NonFinalUnion { }; class FinalClass final { }; struct NonFinalClass { }; class Incomplete; int main() { using std::is_final; static_assert( is_final<const volatile FinalUnion>{}, ""); static_assert(!is_final<FinalUnion[]>{}, ""); static_assert(!is_final<FinalUnion[1]>{}, ""); static_assert(!is_final<NonFinalUnion>{}, ""); static_assert( is_final<FinalClass>{}, ""); static_assert(!is_final<FinalClass&>{}, ""); static_assert(!is_final<FinalClass*>{}, ""); static_assert(!is_final<NonFinalClass>{}, ""); static_assert(!is_final<void>{}, ""); static_assert(!is_final<int>{}, ""); static_assert(!is_final<Incomplete>{}, ""); // error incomplete type 'Incomplete' used in type trait expression }
[2014-02-14, Issaquah: Move to Immediate]
This is an important issue, that we really want to solve for C++14.
Move to Immediate after polling LEWG, and then the NB heads of delegation.
Proposed resolution:
This wording is relative to N3797.
Change 21.3.3 [meta.type.synop], header <type_traits>
synopsis, as indicated
namespace std { […] // 20.10.4.3, type properties: […] template <class T> struct is_empty; template <class T> struct is_polymorphic; template <class T> struct is_abstract; template <class T> struct is_final; […] }
Change 21.3.5.4 [meta.unary.prop], Table 49 — Type property predicates, as indicated
Template | Condition | Preconditions |
---|---|---|
template <class T> struct is_abstract;
|
[…] | […] |
template <class T> struct is_final;
|
T is a class type marked with the class-virt-specifier final (11 [class]).[Note: A union is a class type that can be marked with final . — end note]
|
If T is a class type, T shall be a complete type
|
After 21.3.5.4 [meta.unary.prop] p5 add one further example as indicated:
[Example:
// Given: struct P final { }; union U1 { }; union U2 final { }; // the following assertions hold: static_assert(!is_final<int>::value, "Error!"); static_assert( is_final<P>::value, "Error!"); static_assert(!is_final<U1>::value, "Error!"); static_assert( is_final<U2>::value, "Error!");— end example]