Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
CaseyCarter opened this issue Jul 2, 2017 · 2 comments
Open

Comments

@CaseyCarter
Copy link
Collaborator

CaseyCarter commented Jul 2, 2017

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:

  • Para 3 "The value_type class template may be specialized on user-defined types." normatively duplicates the allowance in the blanket wording in C++14 [namespace.std].
  • Paras 4 "When instantiated with a type I such that I::value_type is valid and denotes a type, value_type<I>::type names that type, unless it is not an object type..." and 5 "When instantiated with a type I such that I::element_type is valid and denotes a type, value_type<I>::type names that type, unless it is not an object type..." redundantly specify requirements that are already explicit in the definition in para 1.

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
Copy link
Owner

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
Copy link
Collaborator Author

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

@CaseyCarter CaseyCarter added TS and removed PDTS labels Jul 16, 2017
@ericniebler ericniebler added C++20 and removed TS labels Apr 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants