2049. is_destructible is underspecified

Section: 21.3.5.4 [meta.unary.prop] Status: C++14 Submitter: Daniel Krügler Opened: 2011-04-18 Last modified: 2016-01-28

Priority: Not Prioritized

View other active issues in [meta.unary.prop].

View all other issues in [meta.unary.prop].

View all issues with C++14 status.

Discussion:

The conditions for the type trait is_destructible to be true are described in Table 49 — Type property predicates:

For a complete type T and given
template <class U> struct test { U u; };,
test<T>::~test() is not deleted.

This specification does not say what the result would be for function types or for abstract types:

  1. For an abstract type X the instantiation test<X> is already ill-formed, so we cannot say anything about whether the destructor would be deleted or not.
  2. In regard to function types, there exists a special rule in the core language, 13.4.2 [temp.arg.type] p. 3, which excludes member functions to be declared via the type of the template parameter:

    If a declaration acquires a function type through a type dependent on a template-parameter and this causes a declaration that does not use the syntactic form of a function declarator to have function type, the program is ill-formed.

    [ Example:

    template<class T> struct A {
      static T t;
    };
    typedef int function();
    A<function> a; // ill-formed: would declare A<function>::t
                   // as a static member function
    

    end example ]

    which has the same consequence as for abstract types, namely that the corresponding instantiation of test is already ill-formed and we cannot say anything about the destructor.

To solve this problem, I suggest to specify function types as trivially and nothrowing destructible, because above mentioned rule is very special for templates. For non-templates, a typedef can be used to introduce a member as member function as clarified in 9.3.4.6 [dcl.fct] p. 10.

For abstract types, two different suggestions have been brought to my attention: Either declare them as unconditionally non-destructible or check whether the expression

std::declval<T&>().~T()

is well-formed in an unevaluated context. The first solution is very easy to specify, but the second version has the advantage for providing more information to user-code. This information could be quite useful, if generic code is supposed to invoke the destructor of a reference to a base class indirectly via a delete expression, as suggested by Howard Hinnant:

template <class T>
my_pointer<T>::~my_pointer() noexcept(is_nothrow_destructible<T>::value)
{
   delete ptr_;
}

Additional to the is_destructible traits, its derived forms is_trivially_destructible and is_nothrow_destructible are similarly affected, because their wording refers to "the indicated destructor" and probably need to be adapted as well.

[ 2011 Bloomington ]

After discussion about to to handle the exceptional cases of reference types, function types (available by defererencing a function pointer) and void types, Howard supplied proposed wording.

[ 2011-08-20 Daniel comments and provides alternatives wording ]

The currently proposed wording would have the consequence that every array type is not destructible, because the pseudo-destructor requires a scalar type with the effect that the expression

std::declval<T&>().~T()

is not well-formed for e.g. T equal to int[3]. The intuitive solution to fix this problem would be to adapt the object type case to refer to the expression

std::declval<U&>().~U()

with U equal to remove_all_extents<T>::type, but that would have the effect that arrays of unknown bounds would be destructible, if the element type is destructible, which was not the case before (This was intentionally covered by the special "For a complete type T" rule in the FDIS).

Suggestion: Use the following definition instead:

Let U be remove_all_extents<T>::type.
For incomplete types and function types, is_destructible<T>::value is false.
For object types, if the expression std::declval<U&>().~U() is well-formed
when treated as an unevaluated operand (Clause 5), then is_destructible<T>::value
is true, otherwise it is false.
For reference types, is_destructible<T>::value is true.

This wording also harmonizes with the "unevaluated operand" phrase used in other places, there does not exist the definition of an "unevaluated context"

Note: In the actually proposed wording this wording has been slightly reordered with the same effects.

Howard's (old) proposed resolution:

Update 21.3.5.4 [meta.unary.prop], table 49:

template <class T> struct is_destructible; For a complete type T and given template <class U> struct test { U u; };, test<T>::~test() is not deleted.
For object types, if the expression: std::declval<T&>().~T() is well-formed in an unevaluated context then is_destructible<T>::value is true, otherwise it is false.
For void types, is_destructible<T>::value is false.
For reference types, is_destructible<T>::value is true.
For function types, is_destructible<T>::value is false.
T shall be a complete type, (possibly cv-qualified) void, or an array of unknown bound.

[2012, Kona]

Moved to Tentatively Ready by the post-Kona issues processing subgroup.

[2012, Portland: applied to WP]

Proposed resolution:

Update 21.3.5.4 [meta.unary.prop], table 49:

template <class T> struct is_destructible; For a complete type T and given template <class U> struct test { U u; };, test<T>::~test() is not deleted.
For reference types, is_destructible<T>::value is true.
For incomplete types and function types, is_destructible<T>::value is false.
For object types and given U equal to remove_all_extents<T>::type,
if the expression std::declval<U&>().~U() is well-formed when treated as an
unevaluated operand (Clause 7 [expr]), then is_destructible<T>::value is true,
otherwise it is false.
T shall be a complete type, (possibly cv-qualified) void, or an array of unknown bound.