2

I found some (for me) unexplainable behaviour in VS2022 and C++20 with static_assert(std::forward_iterator<my_iterator<...>>), looks to me broken or I'm not using it in a correct fasion. But searching the internet I can not find any reference to or menioning of this kind of problems for it, also not here.

added introduction (as suggedted by JaMiT) >>> A container and iterator are set up following an example from 'Template Metaprogramming with C++' (Marius Bancila) to the letter for the API part. The iterator is set up as a forward iterator. Testing it with static_assert(std::forward_iterator<my_iterator<...>>) fails and I do not know why or how to fix it.

Testing the example code from 'Template Metaprogramming with C++' shows the same behaviour. However the static assert is not done in the example.

Note: some suggestions made by @康桓瑋 and @Barry give some good indications on what is not correct. <<<

I have read 'Template Metaprogramming with C++' (Marius Bancila, isbn 978-1-80324-345-0) Tested several of the examples and extended on that for my own use. All using the C++20 standard in VS2022.

While building my own container and iterator I ran in some problems and I can't find out what the cause is or how to fix it.

I have

template <typename Value, typename Key = std::string>
class my_container
...

template<typename Value, typename Key>
class my_iterator
...

The iterator is set up as a forward iterator and I want to test is with

static_assert(std::forward_iterator<my_iterator<int, std::string>>);

which fails.

It should also pass as an input iterator but a test for that also fails.

static_assert(std::input_iterator<my_iterator<int, std::string>>);

The build results is as follows:

Build started at 16:36...
1>------ Build started: Project: TestForwardIteratorConsole, Configuration: Debug x64 ------
1>TestForwardIteratorConsole.cpp
1>    ...\TestForwardIteratorConsole\TestForwardIteratorConsole.cpp(7,20): error C2607: static assertion failed
1>    ...\TestForwardIteratorConsole\TestForwardIteratorConsole.cpp(7,20):
1>    the concept 'std::input_iterator<my_iterator<int,std::basic_string<char,std::char_traits<char>,std::allocator<char>>>>' evaluated to false
1>        C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility(984,59):
1>        the concept 'std::indirectly_readable<my_iterator<int,std::basic_string<char,std::char_traits<char>,std::allocator<char>>>>' evaluated to false
1>            C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility(898,31):
1>            the concept 'std::_Indirectly_readable_impl<my_iterator<int,std::basic_string<char,std::char_traits<char>,std::allocator<char>>>>' evaluated to false
1>                C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility(891,21):
1>                the concept 'std::same_as<const int&,int&>' evaluated to false
1>                    C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility(891,21):
1>                    'const int &' and 'int &' are different types
1>                C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility(891,11):
1>                the expression was resolved to a call to 'my_iterator<int,std::basic_string<char,std::char_traits<char>,std::allocator<char>>>::operator *'
1>                ...\TestForwardIteratorConsole\MyIterator.h(174,21):
1>                see declaration of 'my_iterator<int,std::basic_string<char,std::char_traits<char>,std::allocator<char>>>::operator *'
1>Done building project "TestForwardIteratorConsole.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 16:36 and took 06,751 seconds ==========

The (for me unexplainable) thing is that the test for input iterator is ok when reference operator*() is removed from the template. But having that is a requirement for the forward iterator.

Back to the book. There is a gitub source for all the example code. One of the examples is building a ringbuffer with iterator. Testing that code for input and forward iterator give the same results!

This suggest to me that in VS2022 static_assert(std::forward_iterator<my_iterator<...>>) is broken.

Who can give me a suggestion on how to fix this?

Here is the code I use for testing the behavour. The actual container and iterate are just for testing the behavior and do not have any further use. It is not very beautiful code but it serves the purpose of showing the problem. The definition of reference operator*() is at line 180 and is in comment.

// MyIterator templates and declarations
#pragma once

#include <string>
#include <map>
#include <iterator>

template<typename Value, typename Key>
class my_iterator;

template <typename Value, typename Key = std::string>
class my_container
{
public:
    using self_type = my_container<Value, Key>;

    using value_type = Value;
    using reference = Value&;
    using const_reference = const Value&;

    using iterator = my_iterator<Value, Key>;
    using const_iterator = my_iterator<Value const, Key>;

    friend my_iterator<Value, Key>;

private:
    std::map<Key, Value> map;

public:
    constexpr my_container() noexcept {}

    my_container(const self_type& other) noexcept : map{ std::map<Key, Value>(other.map) } {}

    my_container(self_type&& other) noexcept : map{ std::map<Key, Value>(other.map) } {}

    virtual ~my_container() {}

    self_type& operator=(self_type& other)
    {
        map = std::map<Key, Value>(other.map);
        return *this;
    }

    self_type& operator=(self_type&& other)
    {
        map = std::map<Key, Value>(other.map);
        return *this;
    }

    std::weak_ordering operator<=>(self_type const& other) const noexcept
    {
        return map <=> other.map;
    }

    bool operator==(self_type const& other) const noexcept
    {
        auto r = (*this <=> other);
        return (r == std::weak_ordering::equivalent);
    }

    bool operator!=(self_type const& other) const noexcept
    {
        auto r = (*this <=> other);
        return (r != std::weak_ordering::equivalent);
    }

    reference operator[](const Key key) noexcept
    {
        if (!map.contains(key)) map[key] = Value();
        return map[key];
    }

    reference operator[](Key&& key) noexcept
    {
        if (!map.contains(key)) map[key] = Value();
        return map[key];
    }

    reference at(const Key key) noexcept(false)
    {
        if (!map.contains(key)) throw std::out_of_range("key unavailable");
        return map[key];
    }

    const_reference at(const Key& key) const noexcept(false)
    {
        if (!map.contains(key)) throw std::out_of_range("key unavailable");
        return map[key];
    }

    void add(const Key key, Value& value) noexcept { map[key] = value; }
    void add(const Key key, Value&& value) noexcept { map[key] = value; }

    iterator begin() { return my_iterator(*this, 0); }
    iterator end() { return iterator(*this, map.size()); }
};

template<typename Value, typename Key>
class my_iterator
{
public:
    using iterator_category = std::forward_iterator_tag;
    using self_type = my_iterator<Value, Key>;
    using container_type = my_container<Value, Key>;

    using value_type = Value;
    using reference = value_type&;
    using const_reference = value_type const&;

    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;

private:
    std::reference_wrapper<container_type> container;
    size_type index;

public:
    explicit my_iterator(
        container_type& container,
        size_type const index) noexcept :
        container(container),
        index(index)
    { }

    my_iterator(const self_type& other) noexcept :
        container(other.container),
        index(other.index)
    {}

    self_type& operator=(const self_type& other) noexcept
    {
        container = std::reference_wrapper<container_type>(other.container);
        index = other.index;
        return *this;
    }

    bool comparable(self_type const& other) const noexcept
    {
        return (order(other) == std::weak_ordering::equivalent);
    }

    std::weak_ordering operator<=>(self_type const& other) const noexcept(false)
    {
        if (!comparable(other)) throw std::logic_error("not comparable");
        return index <=> other.index;
    }

    bool operator==(self_type const& other) const noexcept(false)
    {
        return (*this <=> other) == std::weak_ordering::equivalent;
    }

    bool operator!=(self_type const& other) const noexcept(false)
    {
        return (*this <=> other) != std::weak_ordering::equivalent;
    }

    self_type& operator++() noexcept(false)
    {
        if (pass_end()) throw std::out_of_range("out of range");
        inc();
        return *this;
    }

    self_type operator++(int) noexcept(false)
    {
        if (pass_end()) throw std::out_of_range("out of range");
        auto r = *this;
        inc();
        return r;
    }

    const_reference operator*() const noexcept(false)
    {
        if (pass_end()) throw std::out_of_range("out of range");
        return lookupval(index);
    }

    //reference operator*() noexcept(false)
    //{
    //    if (pass_end()) throw std::out_of_range("out of range");
    //    return lookupval(index);
    //}

    const_reference operator->() const noexcept(false)
    {
        if (pass_end()) throw std::out_of_range("out of range");
        return lookupval(index);
    }

    reference operator->() noexcept(false)
    {
        if (pass_end()) throw std::out_of_range("out of range");
        return lookupval(index);
    }

private:
    bool pass_end() const { return ( container.get().map.size() <= index ); }

    void inc() noexcept { if (!pass_end()) index++; }

    std::weak_ordering order(self_type const& other) const noexcept
    {
        return container.get() <=> other.container.get();
    }

    Key& lookup(size_type n) const noexcept(false)
    {
        size_type count{ 0 };
        for (auto& [key, value] : container.get().map)
        {
            if (count == n) return key;
            count++;
        }
        throw std::out_of_range("out of range");
    }

    reference lookupval(size_type n) noexcept(false)
    {
        size_type count{ 0 };
        for (auto& [key, value] : container.get().map)
        {
            if (count == n) return value;
            count++;
        }
        throw std::out_of_range("out of range");
    }
};

Edit after comment from @HolyBlackCat

This is some code to test is, safe the previous code in "MyInterator.h" (And I'm sorry for the size of it but I did not see how to make it shorter without breaking it).

#include <iostream>
#include "MyIterator.h"

static_assert(std::input_iterator<my_iterator<int, std::string>>);
//static_assert(std::forward_iterator<my_iterator<int, std::string>>);

int main()
{
    std::cout << "Hello World!" << std::endl;

    try
    {
        my_container<int> container{};
        auto count{ 0 };
        for (auto item : container)
        {
            std::cout << ++count << ": " << item << std::endl;
        }
    }
    catch (...)
    {
        std::cout << "Exception corrured" << std::endl;
    }
    std::cout << "Goodby" << std::endl;

}
11
  • 2
    Please post a minimal reproducible example, emphasis on minimal. Commented Aug 26 at 14:50
  • Added the c++ tag because the base language tag should always be present. Commented Aug 26 at 14:51
  • 1
    forward_iterator requires that iterator must be default-constructible, which is what you are missing. Commented Aug 26 at 14:56
  • 1
    This question looks like it was written as a story instead of as a question. In addition, it's like the opening paragraph actively avoids explaining what the question is (it mentions "unexplainable behaviour" without stating what the behavior is nor why the behaviour cannot be explained). Could you write an opening paragraph that focuses on the issue instead of on your experiences? (See also How to Ask.) Commented Aug 26 at 15:01
  • 1
    "This is some code to test is, safe the previous code in "MyInterator.h"" Ideally we want a single code block that we can paste into gcc.godbolt.org Commented Aug 26 at 15:12

1 Answer 1

10

The (for me unexplainable) thing is that the test for input iterator is ok when reference operator*() is removed from the template. But having that is a requirement for the forward iterator.

Iterators must be shallow-const. They're generalizations of pointers. This means that operator*() must be a const member function, and this is checked by a requirement that *it and *std::as_const(it) have the same type.

In your code you have two overloads:

const_reference operator*() const noexcept(false);
reference operator*() noexcept(false);

That is incorrect, there needs to be only one overload — the const one. Whether that should return reference or const_reference depends on what you want this iterator to do.

The same is true for operator->(). You don't strictly need to provide it for C++20 ranges, but if you do provide it, it should be a const member function.


Now, the erroneous operator is commented out in the question right now. That makes your iterator a valid input iterator, but it fails to be a valid forward iterator because forward iterators need to be default constructible. So you need to add a default constructor — which means that your container member needs to become a pointer rather than a reference_wrapper.

Once you add a default constructor, your type is a valid forward iterator too.

Sign up to request clarification or add additional context in comments.

6 Comments

thanks for the suggestions, I will follow it up. My comment on the missing default .ctor and overloading operator*() and operator->() is that I followed exactly what Marius Bancila is doing in his book.
@PapaAtHome If the book is showing that you should provide const and non-const overloads operator* and operator-> for an iterator, then that is an error and you should get the author to fix it.
@PapaAtHome I should also note that other parts of this don't make much sense. Your order() function is returning a weak_ordering between the two container_type pointers. But you don't need an ordering — you only care whether the pointers are the same. So you could just have comparable() return contianer.get() == other.container.get(). Also operator-> needs to return a pointer, not a reference.
I will contact the autor on the use of const and non-const. That part is an exact copy from the examples. The weak_ordering is my addition and will give it another look.
Sent Marius Bancila a message through the publisher. The one thing I don't get is the use of a default .ctor for an iterator. Instantiating an iterator with it gives a nonsence iterator, no reference to a container. Useless(?!). I have to think about it.
|

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.