2133. Attitude to overloaded comma for iterators

Section: 16.4.6.4 [global.functions] Status: C++17 Submitter: Yakov Galka Opened: 2012-01-25 Last modified: 2017-07-30

Priority: 3

View all other issues in [global.functions].

View all issues with C++17 status.

Discussion:

16.4.6.4 [global.functions] says

Unless otherwise specified, global and non-member functions in the standard library shall not use functions from another namespace which are found through argument-dependent name lookup (3.4.2).

This sounds clear enough. There are just two problems:

  1. Both implementations I tested (VS2005 and GCC 3.4.3) do unqualified calls to the comma operator in some parts of the library with operands of user-defined types.

  2. The standard itself does this in the description of some algorithms. E.g. uninitialized_copy is defined as:

    Effects:

    for (; first != last; ++result, ++first)
      ::new (static_cast<void*>(&*result))
        typename iterator_traits<ForwardIterator>::value_type(*first);
    

If understood literally, it is required to call operator,(ForwardIterator, InputIterator).

For detailed discussion with code samples see here.

Proposal:

  1. Add an exception to the rule in 16.4.6.4 [global.functions] by permitting the implementation to call the comma operator as much as it wants to. I doubt we want this. or
  2. Fix the description of the said algorithms and perhaps add a note to 16.4.6.4 [global.functions] that brings attention of the implementers to avoid this pitfall.

[2013-03-15 Issues Teleconference]

Moved to Open.

There are real questions here, that may require a paper to explore and answer properly.

[2014-05-18, Daniel comments and suggests concrete wording]

Other issues, such as 2114 already follow a similar spirit as the one suggested by bullet 2 of the issue submitter. I assert that consideration of possible user-provided overloads of the comma-operator were not intended by the original wording and doing so afterwards would unnecessarily complicate a future conceptualization of the library and would needlessly restrict implementations.

I don't think that a paper is needed to solve this issue, there is a simply way to ensure that the code-semantics excludes consideration of user-provided comma operators. The provided wording below clarifies this by explicitly casting the first argument of the operator to void.

[2015-05, Lenexa]

DK: is putting it in the middle the right place for it?
STL: either works, but visually putting it in the middle is clearer, and for "++it1, ++i2, ++it3" it needs to be done after the second comma, so "++it1, (void) ++i2, (void) ++it3" is better than "(void) ++it1, ++i2, (void) ++it3"
ZY: for INVOKE yesterday we used static_cast<void> but here we're using C-style cast, why?
STL: for INVOKE I want to draw attention that there's an intentional coercion to void because that's the desired type. Here we only do it because that's the best way to prevent the problem, not because we specifically want a void type.
Move to Ready: 9 in favor, none opposed, 1 abstention

Proposed resolution:

This wording is relative to N3936.

  1. Change 26.11.5 [uninitialized.copy] as indicated:

    template <class InputIterator, class ForwardIterator>
      ForwardIterator uninitialized_copy(InputIterator first, InputIterator last,
                                         ForwardIterator result);
    

    -1- Effects:

    for (; first != last; ++result, (void) ++first)
      ::new (static_cast<void*>(&*result))
        typename iterator_traits<ForwardIterator>::value_type(*first);
    

    […]

    template <class InputIterator, class Size,class ForwardIterator>
      ForwardIterator uninitialized_copy_n(InputIterator first, Size n,
                                           ForwardIterator result);
    

    -3- Effects:

    for (; n > 0; ++result, (void) ++first, --n)
      ::new (static_cast<void*>(&*result))
        typename iterator_traits<ForwardIterator>::value_type(*first);
    
  2. Change 26.8.11 [alg.lex.comparison] p3 as indicated:

    template<class InputIterator1, class InputIterator2>
      bool
        lexicographical_compare(InputIterator1 first1, InputIterator1 last1,
                                InputIterator2 first2, InputIterator2 last2);
    template<class InputIterator1, class InputIterator2, class Compare>
      bool
        lexicographical_compare(InputIterator1 first1, InputIterator1 last1,
                                InputIterator2 first2, InputIterator2 last2,
                                Compare comp);
    

    -3- Remarks: […]

    for ( ; first1 != last1 && first2 != last2 ; ++first1, (void) ++first2) {
      if (*first1 < *first2) return true;
      if (*first2 < *first1) return false;
    }
    return first1 == last1 && first2 != last2;