1

One of the problems Deducing this solves is duplication by making member functions cvref aware of the cvref-ness of the object the function is being called on. By declaring Alias templates (Template Typdefs) and returning them in the member functions rather than just returning auto are we not forced to have two iterator objects?

template <typename T>
struct const_list_iterator{

    typedef const T& reference;    
    typedef const T* pointer;

    reference operator*() const{/***/}

    pointer operator ->() const{/***/}
 };

and

 template <typename T >
 struct list_iterator{

    typedef T& reference;    
    typedef T* pointer;

    reference operator*(){/***/}

    pointer operator ->(){/***/}
 };

However, with Deducing this if the return type is auto can we simply have a single iterator object?

template <typename T>
struct list_iterator 
    
    template <typename itself>
    auto operator*(this itself&& self){/***/}

    template <typename itself>
    auto operator->(this itself&& self){/***/}
 };

Which we can use as below:

 template <typename T>
 struct list{
     using iterator = list_iterator<T>;

      auto begin() -> iterator {
          iterator(node -> next); 
      }

      auto begin() const -> iterator{
          iterator(node -> next)
      }

 };

So, will using typedefs force us to duplicate iterator objects?

3
  • 5
    Are you asking about the iterator types themselves or the begin/end overloads? Commented Dec 3, 2022 at 12:18
  • "When we write iterators for collections we make two, one non-const iterator and the const" Strictly-speaking, you never needed that to begin with. If your iterator is a template of the value_type of the collection, you could have made your "const iterator" type use a const value_type. Commented Dec 3, 2022 at 14:42
  • 1
    Your proposal allows me to mutate a const list<T> list: list.begin() returns a list_iterator<T>, which I then dereference with * to access the first list element and modify it. The const version of begin() needs to return a list_iterator<const T>. Commented Dec 3, 2022 at 17:55

1 Answer 1

3

Your example code does not propagate const correctly from the container to the iterator. begin() const will return the same type as begin(). So any deduction on the operator* of the iterator is irrelevant; the fact that const was involved has already been lost.

In order to propagate the const from the container's begin to the iterator, begin must return a different type when the container is called via const. It could be a different specialization of the same iterator template (list_iterator<const T>, for example), but it needs to be a different type.

You can give begin an explicit object parameter and deduce the const-ness from it. But it'd just be doing this:

template <typename Self>
auto begin(this Self&& self)
{
  if constexpr(std::is_const_v<Self>)
    return list_iterator<const T>(...);
  else
    return list_iterator<T>(...);
}

If the if constexpr condition bothers you, you can stick it in a helper type metafunction:

template<typename T, typename U>
  requires std::is_const_v<U>
struct propagate_const { using type = const T; };

template<typename T, typename U>
struct propagate_const { using type = T; };

template<typename T, typename U>
using propagate_const_t = propagate_const<T, U>::type;

template <typename Self>
auto begin(this Self&& self)
{
  return list_iterator<propagate_const_t<T, Self>>(...);
}
Sign up to request clarification or add additional context in comments.

7 Comments

I read this as a very roundabout "yes, we have to duplicate but not the entire iterator" answer to the question. Is that correct?
@Ramon: Where's the duplication? You don't have to write two iterators like the OP said. You don't even have to write two functions. You can write one function which returns two different types based on the const-ness of this.
But these two functions or two branches do exactly the same thing apart from the const modifier which is exactly the kind of problem deducing this was supposed to solve. Deducing this exists to solve this type of const non-const duplication so it's very much considered duplication. So much so that it got its own language feature to fix it. But it seems this case is not really covered by the feature.
@Ramon: The problem you're citing is a problem with how C++ propagates const. If a type T has a member U*, a const T has a member of type U * const, not U const*. So long as this is the case, what you want can't happen. However, you could easily make a template called const_if_const_t<T, U> which results in T if U is not const, and results in const T if U is const. Then just return list_iterator<const_if_const_t<T, Self>>(...);
@Ramon: I've added an example of the metafunction approach, if having an explicit condition bothers you so much.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.