Are c++20 iterators "convertible" to LegacyIterators? - c++

As I understand it, before C++20 iterators were just a concept defined by the standard. Now in C++20 they are real language concepts which are checked at compile time. I'm wondering if I'm safe to assume that I can consume a C++20 iterator in my API and pass it to any pre-C++20 API without it breaking:
Demo
#include <string>
#include <concepts>
#include <cstdio>
#include <utility>
auto write_string(std::input_iterator auto it) -> void { // <-- C++20 iterator
std::string ret = "Danger";
std::copy(ret.begin(), ret.end(), it); // <-- requires LegacyInputIterator
}
int main()
{
std::string str = "Austin is my middle name";
write_string(str.begin());
printf("%.*s\n", (int)str.size(), str.data());
}

std::copy requires that the type of the third parameter must meet the requirements of LegacyOutputIterator, that is, it must support the writing operation, which is not guaranteed by std::input_iterator.
In C++20, the corresponding concept is std::output_iterator, so you can redeclare write_string as
void write_string(std::output_iterator<const char&> auto it) {
std::string ret = "Danger";
std::copy(ret.begin(), ret.end(), it);
}
This guarantees that the type of it satisfies the syntax requirements for output iterators written to char.
(Although the C++20 iterator system is very different from C++98/C++17, the requirements for output iterators are basically equivalent. so in your example, using std::output_iterator to check the requirement of LegacyOutputIterator is fine)

No.
The old LegacyInputIterator concept also required std::equality_comparable, which is no longer required for std::input_iterator.
Therefore, a C++20 iterator type that's not std::equality_comparable is not a LegacyInputIterator and might break old API's.
std::output_iterator however is not required to be std::equality_comparable.

Related

Can not deduce type of ranges iterator

The following code fails to compile both on MSVC 2022 and gcc 12.1 (with c++20 enabled) with the same error:
#include <iostream>
#include <random>
#include <ranges>
#include <vector>
using namespace std;
int main()
{
auto v0 = views::iota(0) | views::take(5) | views::transform([](auto i) -> double { return 10.1 * double(i); });
// output: 0 10.1 20.2 30.3 40.4
// output: double
for (auto i = v0.begin(); i != v0.end(); ++i)
cout << *i << ' ';
cout << endl << typeid(decltype(*v0.begin())).name() << endl;
// error: template argument deduction failed
discrete_distribution<int>::param_type p0(v0.begin(), v0.end()); // (**)
// this is ok
vector<double> v1{10.1, 20.2, 30.3};
discrete_distribution<int>::param_type p1(v1.begin(), v1.end());
return 0;
}
The line (**) generates an error that the type of the param_type ctor iterators can not be deduced:
std::discrete_distribution<int>::param_type::param_type(_InIt,_InIt)': template parameter '_InIt' is ambiguous
Why does the deduction of this iterator type fail with both compiler? The output loop works fine with the same iterator.
How can this be fixed?
The problem is that many types in C++ were created under the pre-C++20 range paradigm. This means that they take a pair of iterators.
All of your views and operations construct ranges based on the C++20 std::ranges paradigm. This means that their ranges can be defined by an iterator and a sentinel value representing the end. The sentinel does not have to be an iterator. And for the case of v0, it does indeed have a sentinel that is not an iterator (this is because iota is a sentinel-based range).
But most types that were written pre-C++20 which conceptually consume ranges still use the paired iterator model of ranges. So they cannot work with any C++20 range that uses a sentinel. The constructor template has one type across two parameters, but your begin/end arguments are different types. Therefore, it fails to deduce the one type.
You will need to convert this range into either a container or a common_range (a range using paired iterators) via the views::common operation.

Any way to trick std::transform into operating on the iterator themselves?

So I wrote this code which won't compile. I think the reason is because std::transform, when given an iterator range such as this will operate on the type pointed to by the iterator, not the iterator itself. Is there any simple wrapper, standard lib tool, etc. to make this code work i.e. to store all the iterators of the original map into a new vector, with minimum changes required? Thanks!
#include <map>
#include <iostream>
#include <vector>
using MT = std::multimap<char, int>;
using MTI = MT::iterator;
int main()
{
MT m;
m.emplace('a', 1); m.emplace('a', 2); m.emplace('a', 3);
m.emplace('b', 101);
std::vector<MTI> itrs;
std::transform(m.begin(), m.end(), std::back_inserter(itrs), [](MTI itr){
return itr;
});
}
EDIT 1: Failed to compile with gcc11 and clang13, C++17/20
EDIT 2: The purpose of the question is mostly out of curiosity. I want to see what's a good way to manipulate existing standard algorithm to work on the level that I want. The sample code and problem are entirely made up for demonstration but they are not related to any real problem that requires a solution
Is there such a wrapper? Not in the standard. But it doesn't mean you can't write one, even fairly simply.
template<typename It>
struct PassIt : It {
It& operator*() { return *this; }
It const& operator*() const { return *this; }
PassIt & operator++() { ++static_cast<It&>(*this); return *this; }
PassIt operator++(int) const { return PassIt{static_cast<It&>(*this)++}; }
};
template<typename It>
PassIt(It) -> PassIt<It>;
That is just an example1 of wrapper that is a iterator of the specified template parameter type. It delegates to its base for the bookkeeping, while ensuring the the return types conform to returning the wrapped iterator itself when dereferencing.
You can use it in your example to simply copy the iterators
std::copy(PassIt{m.begin()}, PassIt{m.end()}, std::back_inserter(itrs));
See it live
(1) - It relies on std::iterator_traits deducing the correct things. As written in this example, it may not conform to all the requirements of the prescribed iterator type (in this case, we aimed at a forward iterator). If that happens, more boiler-plate will be required.
The function you pass to std::transform and algorithms in general are supposed to use elements not iterators. You could use the key to find the iterator in the map, though thats neither efficient nor simple. Instead use a plain loop:
for (auto it = m.begin(); it != m.end(); ++it) itrs.push_back(it);

Why does a std::variant compile with begin and end iterators?

It seems that the compiler should be able to catch the fact that std::variant doesn't have iterator methods, but it seems that my code compiles with no problem (even if I randomly make up methods or member variables for the variant), but it crashes at runtime (rightfully so). Can someone shed some light on why this code compiles?
Note: this isn't blocking progress because now I'm using std::visit, but it would be nice to know why this is compiling.
I have tried using different variant patterns and they all compile. See code examples. You can pop this in to cppreferences, or godbolt and it should compile with C++17 flags or greater
#include <variant>
#include <string>
#include <cassert>
#include <iostream>
#include <list>
#include <map>
template<typename K, typename V>
//using var_maps = std::variant<std::map<K,V>, std::multimap<K,V> >;
//using var_maps = std::variant<std::list<int>, std::list<float> >;
using var_maps = std::variant<int, float>;
template <typename K, typename V>
void flat( const var_maps<K,V>& vmap)
{
//for(auto bIter = vmap.bexxxgin(), eIter = vmap.end(); bIter != eIter;
for(auto bIter = vmap.begin(), eIter = vmap.end(); bIter != eIter;
bIter = vmap.upper_bound( bIter->first ) )
{
}
}
My initial case was with maps, but it effectively compiles with anything. Additionally I can randomly replace begin() to any other word and it still compiles. I know the right way to do this is with visits. I am inevitably trying to have one function that handles both map and multimap and transform it to another data structure.
Thank you!
Your code compiles, because begin() and end() are dependent names - they depend on the function template arguments, so their lookup is postponed until the flat template instantiation. But it is never instantiated!
If you add the following, your code won't compile anymore:
int main () {
&flat<int, int>;
}
It "compiles" because the function is a template. No code is generated here and beyond basic syntax checking no complete checking can be done when the template is parsed.
This is because the compiler does not know whether the var_maps<K,V> contains begin() or not. There could be specializations.
You will receive the error when you instantiate var_maps, i.e. use var_maps with concrete types K and V.

Unexpected results from boost::lexical_cast<int> with boost::iterator_range

I tried converting a substring (expressed by a pair of iterators) to an integer by boost::lexical_cast:
#include <iostream>
#include <boost/lexical_cast.hpp>
int main()
{
// assume [first, last) as substring
const std::string s("80");
auto first = s.begin(), last = s.end();
std::cout << boost::lexical_cast<int>(boost::make_iterator_range(first, last)) << std::endl;
return 0;
}
Output: (wandbox)
1
I got expected result (80) by workaround: boost::make_iterator_range(&*first, last - first).
Question: Why above code does not work as expected? And, where does 1 come from?
lexical_cast does not support iterator_range<std::string::(const_)iterator>
misuse of lexical_cast or iterator_range
bugs of lexical_cast or iterator_range
some other reason
The short answer is number 2 from your list, misuse of iterator_range - specifically you're using it without explicitly including the proper header for it.
Adding this:
#include <boost/range/iterator_range.hpp>
will make it behave as you expect.
The iterator_range and related functionality is split into two headers, iterator_range_core.hpp and iterator_range_io.hpp. The first one contains the class definition, the second one contains, among other things, the operator<< overload which makes it streamable and so usable by lexical_cast (usable in the sense that it will actually work as you expect).
Because you didn't included the proper header, you should normally get a compiler error, but in this case you're not getting it because lexical_cast.hpp includes the first of those two headers, iterator_range_core.hpp. This makes everything build fine, but it doesn't get the operator<< from the second header. Without that overload, when lexical_cast writes the range to the stream to perform the conversion, the best overload it finds is the one taking a bool parameter (because iterator_range has a default conversion to bool). That's why you're seeing that 1, because it's actually writing true to the underlying conversion stream.
You can test this easily with something like this:
auto someRange = boost::make_iterator_range(first, last);
std::cout << std::boolalpha<< someRange;
Without #include <boost/range/iterator_range.hpp> this will print true, with that include it will print your string (80).

Iterating Streams in Reverse

I would like to use std::find_if to traverse the contents of an std::streambuf in reverse. This involves constructing an std::reverse_iterator from an std::istream_iterator or std::istreambuf_iterator. Unfortunately, trying to do this, as shown in the code sample below, results in a compilation error. How can I get this to work? If necessary, solutions using Boost would be great.
#include <cstddef>
#include <fstream>
#include <iterator>
template <class Iterator>
static std::reverse_iterator<Iterator>
make_reverse_iterator(Iterator i)
{
return std::reverse_iterator<Iterator>(i);
}
int main()
{
std::ifstream is{"foo.txt", std::ios::binary};
std::istreambuf_iterator<char> i{is};
auto r = make_reverse_iterator(i);
// Error =(
*r;
return EXIT_SUCCESS;
}
Here is the compilation error reported by g++-4.8.1:
In file included from /opt/local/include/gcc48/c++/bits/stl_algobase.h:67:0,
from /opt/local/include/gcc48/c++/bits/char_traits.h:39,
from /opt/local/include/gcc48/c++/ios:40,
from /opt/local/include/gcc48/c++/istream:38,
from /opt/local/include/gcc48/c++/fstream:38,
from ri.cpp:9:
/opt/local/include/gcc48/c++/bits/stl_iterator.h: In instantiation of 'std::reverse_iterator<_Iterator>::reference std::reverse_iterator<_Iterator>::operator*() const [with _Iterator = std::istream_iterator<char>; std::reverse_iterator<_Iterator>::reference = const char&]':
ri.cpp:24:3: required from here
/opt/local/include/gcc48/c++/bits/stl_iterator.h:163:10: error: no match for 'operator--' (operand type is 'std::istream_iterator<char>')
return *--__tmp;
^
Thanks for your help!
As far as I know input iterators (such as those of ifstreams) are not capable of going backwards which is why the reverse iterator is not available. Which makes sense because if you think about it, the forward of the reverse_iterator (i.e. operator ++) is the backwards of the normal iterator (i.e. operator --) and so if the normal iterator doesn't provide operator --, then it stands to reason that the reverse_iterator should not exist.
As I recall there are 3 types of iterators: forward, bidirectional and random access. Forward can only go in one direction (guess which :P), bidirectional can go forward and backwards by 1 and random access can go forwards and backwards by whatever increment.
As you can see random access iterators offer all the operations of bidirectional iterators (and more) who themselves offer all the operations of forward iterators (and more). Which means random access iterators can be used where forward iterators are require but not the other way around.
As you may have guessed from this explanation make_reverse_iterator most likely requires either bidirectional or random access iterators and ifstream most likely offers only forward which is why the template instantiation fails.