ericniebler / stl2

LaTeX and Markdown source for the Ranges TS/STL2 and associated proposals
87 stars 8 forks source link

value_type: enable_if and ::value_type + ::element_type ambiguity #423

Open CaseyCarter opened 7 years ago

CaseyCarter commented 7 years ago

Many people have commented on the oddness of using enable_if in the specification of value_type in [iterator.assoc.types.value_type] (shown here after applying #299):

template <class> struct value_type { };

template <class T>
struct value_type<T*>
  : enable_if<is_object<T>::value, remove_cv_t<T>> { };

template <class I>
  requires is_array<I>::value
struct value_type<I> : value_type<decay_t<I>> { };

template <class I>
struct value_type<I const> : value_type<decay_t<I>> { };

template <class T>
  requires requires { typename T::value_type; }
struct value_type<T>
  : enable_if<is_object<typename T::value_type>::value, typename T::value_type> { };

template <class T>
  requires requires { typename T::element_type; }
struct value_type<T>
  : enable_if<is_object<typename T::element_type>::value,
      remove_cv_t<typename T::element_type>> { };

template <class T> using value_type_t
  = typename value_type<T>::type;

The reason for the use of enable_if in e.g. the element_type case is simple to explain: we want this specialization to be used when T has a member type element_type even if it specifies a non-object type so that value_type_t<T> is properly ambiguous for a T that specifies both element_type and value_type. Nonetheless, many readers' first reaction is confusion: "I thought Concepts meant not having to use enable_if anymore?!?" It would reduce reader confusion to eliminate the metaprogramming and use only associated constraints in the definition of value_type.

Further, the specification of value_type is deliberately ambiguous when the type T has both a member type value_type and a member type element_type: the idea here is that the implementer of T should specialize value_type explicitly to disambiguate. I speculate that there is a sizable body of types in the wild with implementers that are confused about whether they should specify value_type or element_type that simply give up and specify both (e.g., the proposed span view which seems to think it's a container and a smart pointer). There's no reason that value_type should refuse to admit such types when the value_type and element_type they specify are consistent.

Finally, the text description of value_type is confused and redundant:

Proposed Resolution

[Editor's note: This wording incorporates the PR of #299. We present two alternatives here: #1 ignores element_type when value_type is present, and #2 requires the two to be consistent when both are present.]

Replace the specification of value_type in [iterator.assoc.types.value_type]/1 with:

template <class> struct value_type { };

template <class T>
  requires is_array<T>::value
struct value_type<T> : value_type<decay_t<T>> { };

template <class T>
struct value_type<T const> : value_type<decay_t<T>> { };

template <class T>
   requires is_object<T>::value
struct value_type<T*> {
   using type = remove_cv_t<T>;
};

template <class T>
concept bool __MemberValueType = // exposition-only
  requires { typename T::value_type; };

template <__MemberValueType T>
struct value_type<T> { };

template <__MemberValueType T>
  requires is_object<typename T::value_type>::value
struct value_type<T> {
  using type = typename T::value_type;
};

template <class T> using value_type_t
  = typename value_type<T>::type;

For alternative 1 (value_type takes precedence over element_type) further append:

template <class T>
concept bool __MemberElementType =  // exposition-only
  requires { typename T::element_type; };

template <__MemberElementType T>
  requires !__MemberValueType<T>
struct value_type<T> { };

template <__MemberElementType T>
  requires is_object<typename T::element_type>::value &&
    !__MemberValueType<T>
struct value_type<T> {
  using type = remove_cv_t<typename T::element_type>;
};

For alternative 2 (value_type and element_type must be consistent) instead append:

template <class T>
concept bool __MemberElementType =  // exposition-only
  requires { typename T::element_type; };

template <__MemberElementType T>
struct value_type<T> { };

template <__MemberElementType T>
  requires is_object<typename T::element_type>::value
struct value_type<T> {
  using type = remove_cv_t<typename T::element_type>;
};

template<class T>
  requires
    __MemberValueType<T> && is_object<typename T::value_type>::value &&
    __MemberElementType<T> && is_object<typename T::element_type>::value &&
    Same<typename T::value_type, remove_cv_t<typename T::element_type>>
struct value_type<T> {
  using type = typename T::value_type;
};

For both alternatives, also replace paragraphs 3 through 5 with:

3 [Note: Pursuant to the requirements of ISO/IEC 14882:2014 §\cxxref{namespace.std} users may specialize value_type to specify the associated value type of a user-defined type.—end note]

4 [Note: Some legacy output iterators define a nested type value_type that is an alias for void. These types are not Readable and have no associated value types.—end note]

5 [Note: Smart pointers like shared_ptr<int> are Readable and have an associated value type. A smart pointer like shared_ptr<void>, however, has no associated value type and is therefore not Readable.—end note]

[Editor's note: Consider replacing uses of I in this section that refer to a type that potentially has an associated value type with T. Use of I is appropriate for types that satisfy Iterator, but value_type has applicability beyond models of iterator. ]

ericniebler commented 6 years ago

I don't love the added complexity here. I would be ok with just picking T::value_type over T::element_type, with a de facto requirement that they be the same when they are both present.

CaseyCarter commented 6 years ago

I would be ok with just picking T::value_type over T::element_type, with a de facto requirement that they be the same when they are both present.

I've included alternative wording for that formulation in the PR. (I have a mild preference to tell users they're making a mistake by defining conflicting value_type and element_type, we'll see what LWG has to say if and when we get around to discussing P4s.)