Movatterモバイル変換


[0]ホーム

URL:



This page is a snapshot from the LWG issues list, see theLibrary Active Issues List for more information and the meaning ofC++14 status.

2112. User-defined classes that cannot be derived from

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.

It seems that both user and library did the best they could: None of the affected types did impose explicitrequirements on the corresponding user-defined types to be derivable from (This capability was not part ofthe required operations), and libraries did apply EBCO whereever possible to the convenience of the customer.

Nonetheless given the existence of non-derivable-from class types in C++11, libraries have to cope withfailing derivations. How should that problem be solved?

It would certainly be possible to add weazel wording to the allocator requirements similar to what we hadin C++03, but restricted to derivation-from requirements. I consider this as the bad solution, because itwould add new requirements that never had existed before in this explicit form onto types like allocators.

Existing libraries presumably will need internal traits like__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:

  1. 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.

  2. 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.

We believe that both a simple and clear definition of theis_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.

  1. 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;  […]}
  2. Change 21.3.6.4[meta.unary.prop], Table 49 — Type property predicates, as indicated

    Table 49 — Type property predicates
    TemplateConditionPreconditions
    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 withfinal. —end note]
    IfT is a class type,T shall be a complete type
  3. 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]


[8]ページ先頭

©2009-2026 Movatter.jp