1

Suppose that I am writing an iterator and const_iterator for singly linked list.

Suppose that I have a following classes:

template <typename T>
struct Node
{
    T value;
    Node* next;
}

template <typename T>
struct NodePtr
{
private:
     Node<T>* node;
public:
     T& operator*() { return node->value; }
     const T& operator*() const { return node->value; }

     // Ommitted increment, comparison and so on...
}

class Iterator<T, bool isConst>
{
     private: NodePtr<T> nodePtr;
     using Reference = std::conditional_t<isConst, const T&, T&>;

     Reference operator*() { return *nodePtr; }
     const Reference operator*() const { return *nodePtr; }

     // Ommited
}

My question is whether it is possible somehow to replace the lines

Reference operator*() { return node->value; }
const Reference operator*() const { return node->value; }

with a single definition (perhaps using the template argument isConst) and have the const specifier deduced by the compiler? I want to have * to be const T& operator*() const when isConst = true and have both versions when isConst = false. Is it possible? If it is - then how to do it?

Raziel Magius
  • 565
  • 1
  • 11
  • 2
    Note that `const Reference` is most probably not what you want. That `const` is the top-level `const`, it won't turn, e.g. `int&` into `const int&`. – Evg May 31 '20 at 21:07
  • Seems that you are right. But this is weird. Are not references already const? Also, why does it work for regular types but acts "weirdly" when used together with std::conditional_t? – Raziel Magius May 31 '20 at 21:29
  • Consider adding `const` to `int*`. I would expect to get "`const (int*)`", i.e. `int* const`, not `const int*`. Loosely speaking, references are already `const`, that's why top-level `const` is simply ignored for references. All this has nothing to do with `std::conditional_t` itself. With `using T = int&`, `const T` and `T` are both `int&`. – Evg May 31 '20 at 21:37
  • Makes sense, although counter intuitive. Thanks. – Raziel Magius May 31 '20 at 22:05
  • But note that formally references are non-const, i.e. `std::is_const_v` returns `false` for them. – HolyBlackCat May 31 '20 at 22:08
  • @HolyBlackCat I would not ask this question have I seen it before, however it is not what I have asked. I am interested in not having the function without const qualifier being generated at all, and enable_if is the answer. – Raziel Magius May 31 '20 at 22:21
  • @RazielMagius My bad, didn't fully read the question. Reopened. – HolyBlackCat May 31 '20 at 22:25

1 Answers1

3

I don't think there is a way to write the function only once. Most things you can do with auto and templates but the problem is the const specifier for the function itself. I know of no way to make that in any form conditional. You could make it always const and then the nodePtr mutable but that kind of defeats the point of the whole thing. What you can do is deactivate the non-const overload for the const_iter by doing the following

template<bool tmp = isConst, std::enable_if_t<!tmp, char> = 0> // you need the tmp so that the enable_if is dependent
Reference operator*() {
    return *nodePtr;
}
n314159
  • 4,701
  • 1
  • 3
  • 20
  • In C++20 this can be reduced to `Reference operator*() requires (!IsConst) {...}`. – HolyBlackCat May 31 '20 at 22:27
  • @HolyBlackCat Visual studio does not support c++20 yet, unfortunately ;-( – Raziel Magius Jun 01 '20 at 19:18
  • @n314159 I think that std::enable_if_t = 0 can be replaced with std::enable_if_t (which is same std::enable_if) I think, as the value is not used, and produces less noise. Am I missing something? – Raziel Magius Jun 01 '20 at 19:21
  • 1
    @RazielMagius `std::enable_if_t` will expand to `void` if the condition is true, and `void` is not a valid type for a template parameter. Try it, it won't work (this overload will never be called). – HolyBlackCat Jun 01 '20 at 19:23
  • @HolyBlackCat What about template >. In this case it does work, I think. – Raziel Magius Jun 01 '20 at 20:09
  • @RazielMagius Yeah, because it expands to `typename = void`, and `void` can be passed into a type template parameter. But it can't be a type of a non-type template parameter: `void X` is invalid. – HolyBlackCat Jun 01 '20 at 20:51
  • @RazielMagius in the comments under this question I learned a bit about that topic.https://stackoverflow.com/questions/59824884/whats-the-point-of-unnamed-non-type-template-parameters?noredirect=1#comment105791006_59824884 – n314159 Jun 02 '20 at 00:13