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;
}
c++tag because the base language tag should always be present.forward_iteratorrequires that iterator must be default-constructible, which is what you are missing.