This page is a snapshot from the LWG issues list, see theLibrary Active Issues List for more information and the meaning ofC++14 status.
Section: 16.4.6[conforming], 20.2.9[allocator.traits], 20.6.1[allocator.adaptor.syn]Status:C++14Submitter: Daniel KrüglerOpened: 2011-11-30Last modified: 2016-01-28
Priority:1
View all otherissues in [conforming].
View all issues withC++14 status.
Discussion:
It is a very established technique for implementations to derive internally from user-defined class types that areused to customize some library component, e.g. deleters and allocators are typical candidates. The advantage of thisapproach 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 theredid exist a portable idiom to prevent that a class type could be derived from, this idiom has never reached greatpopularity: Thetechnique requiredto introduce a virtual base class and it did not really prevent the derivation, but only any construction ofsuch a type. Further, such types are notempty as defined by thestd::is_empty trait, socould 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 fortypes 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 Noopfinal { template<class Ptr> void operator()(Ptr) const {}};std::unique_ptr<int, Noop> up;In addition, manystd::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_derivableto make EBCO possible in the current form but excluding non-derivable class types. As of this writing thisseems to happen already. Problem is that without astd::is_derivable trait, third-party librarieshave 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 issueshould 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 betweenstd::auto_ptr andstd::auto_ptr_ref possible) and that it would be better toprovide 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 ofscoped_allocator_adaptor as of20.6.1[allocator.adaptor.syn] already requires derivation fromOuterAlloc, but only implies indirectly the same for the inner allocators due to theexposition-only description of memberinner. 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 reportsClang 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 traitthat 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 theclass-virt-specifierfinal — 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 andwould be more useful for non-experts. One problem with that request is that it is astonishingly hard to find a common denominator for what theprecise 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 theis_empty traitshould be (which can be considered as a kind of higher-level trait).Once we have a fundamental trait likeis_final available, we can easily define higher-level traits in the futureon 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 solvethis dilemma. We assert that since C++11 the static introspection capabilities are already very large and we believe thatmaking the presence or absence of thefinal 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 itunion FinalUnionfinal { };union NonFinalUnion { };class FinalClassfinal { };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.6.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 theclass-virt-specifierfinal (11[class]).[Note: A union is a class type that can be marked with final. —end note] | IfT is a class type,T shall be a complete type |
After 21.3.6.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]