Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make pqxx iterators model the right iterator category #846

Closed
alexolog opened this issue Jun 7, 2024 · 9 comments · Fixed by #860
Closed

Make pqxx iterators model the right iterator category #846

alexolog opened this issue Jun 7, 2024 · 9 comments · Fixed by #860

Comments

@alexolog
Copy link

alexolog commented Jun 7, 2024

When using C++20 concepts:

#include <iterator>
#include <print>

#include "pqxx/pqxx"

template<typename T> consteval std::string_view classify()
{
    if constexpr (std::contiguous_iterator<T>)
        return "contiguous_iterator";
    if constexpr (std::random_access_iterator<T>)
        return "random_access_iterator";
    if constexpr (std::bidirectional_iterator<T>)
        return "bidirectional_iterator";
    if constexpr (std::forward_iterator<T>)
        return "forward_iterator";
    if constexpr (std::input_iterator<T>)
        return "input_iterator";
    if constexpr (std::input_or_output_iterator<T>)
        return "input_or_output_iterator";
    return "not an iterator";
}

#define TEST(x) std::println("{} : {}", #x, classify<x>())

int main()
{
    TEST(pqxx::const_row_iterator);
    TEST(pqxx::const_result_iterator);
}

Results in:

pqxx::const_row_iterator : bidirectional_iterator
pqxx::const_result_iterator : bidirectional_iterator

Even though those iterators are "supposed" to model std::random_access_iterator

@jtv
Copy link
Owner

jtv commented Jun 7, 2024

I thought I did the work for this, sometime long ago.

@alexolog do you know exactly which concepts are missing, and how I implement them?

@jtv
Copy link
Owner

jtv commented Jun 8, 2024

I think we should be able to get some of the information by trying to instantiate a template that expects a std::random_access_iterator, but passing it a pqxx::const_row_iterator or a pqxx::const_result_iterator. There should be an error, and at least with some compilers, the error will tell us "this type doesn't satisfy the random_access_iterator concept because..."

@alexolog did you see a helpful error like that while compiling your code? (Switching between gcc and clang may help — I think they produce different error messages.

@alexolog
Copy link
Author

alexolog commented Jun 9, 2024

I thought I did the work for this, sometime long ago.

@alexolog do you know exactly which concepts are missing, and how I implement them?

Looking at LegacyRandomAccessIterator, we can see the requirements:

template< class I >
concept random_access_iterator =
	std::bidirectional_iterator<I> &&
	std::derived_from</*ITER_CONCEPT*/<I>, std::random_access_iterator_tag> &&
	std::totally_ordered<I> &&
	std::sized_sentinel_for<I, I> &&
	requires(I i, const I j, const std::iter_difference_t<I> n) {
		{ i += n } -> std::same_as<I&>;
		{ j +  n } -> std::same_as<I>;
		{ n +  j } -> std::same_as<I>;
		{ i -= n } -> std::same_as<I&>;
		{ j -  n } -> std::same_as<I>;
		{  j[n]  } -> std::same_as<std::iter_reference_t<I>>;
	};

It appears you have most of them, and the only one missing is operator[]. Implement that one and you're golden.

@alexolog
Copy link
Author

alexolog commented Jun 9, 2024

I think we should be able to get some of the information by trying to instantiate a template that expects a std::random_access_iterator, but passing it a pqxx::const_row_iterator or a pqxx::const_result_iterator. There should be an error, and at least with some compilers, the error will tell us "this type doesn't satisfy the random_access_iterator concept because..."

@alexolog did you see a helpful error like that while compiling your code? (Switching between gcc and clang may help — I think they produce different error messages.

See the example code I included in the original post.
We can find the iterator category by using if constexpr instead of SFINAE.
Both result and row iterator types satisfy bidirectional_iterator and below, but not random_access_iterator or above.

@jtv
Copy link
Owner

jtv commented Jun 9, 2024

Implementing operator[] isn't so easy. The result iterator already has an operator, but it does something different.

@alexolog alexolog changed the title Make pqxx iterators model std::random_access_iterator Make pqxx iterators model the right iterator category Jun 9, 2024
@alexolog
Copy link
Author

alexolog commented Jun 9, 2024

Implementing operator[] should be easy since
image

Giving it different semantics (or omitting it altogether) means that the iterator won't be treated as a random-access iterator by libraries (including the standard library), which will, at best, choose to invoke operator++ in a loop instead of the more efficient operator+ (e.g., std::distance), or flatly refuse to accept it in some cases (e.g., std::flat_set<> or std::sort)

This is of course a valid design choice, but you cannot have your cake and eat it too. So the options are:

  1. (re)implement operator[] with the expected semantics to have the iterators model std::random_access_iterator, or
  2. declare the iterators as bidirectional (using iterator_category = std::bidirectional_iterator_tag;) and document them as such.

(Issue renamed accordingly)

@jtv
Copy link
Owner

jtv commented Jun 16, 2024

@alexolog for some reason I think some conversation disappeared. But you're right — I wasn't aware of the operator[] requirement back when I wrote that code. So the result iterator is going to have to be a bidirectional iterator for now.

We'll probably need a deprecation period for the existing operator[] in the result iterator — which means we can't fully resolve this in 8.0. :-( On the one hand I see why the concept has that requirement (given how the classic C definition of the operator is just a combination of * and +). On the other hand it really gets in the way of the "referential transparency" that made so much sense in a two-dimensional iteration.

OTOH the row iterator is easy — that didn't have an operator[]. So in the short term I'm thinking...

  1. Degradate the result iterators to "bidirectional."
  2. Give the row iterators an operator[].

@jtv
Copy link
Owner

jtv commented Jun 30, 2024

We should be able to resolve this together with #770. Rewrite the iterator types so that they're no longer closely intertwined with the row/field types; give row and const_result_iterator different operator[]s with different return types; and also make row and field keep their result object alive but no longer make the result/row iterators keep their result alive.

jtv added a commit that referenced this issue Jul 5, 2024
Fixes: #846

This involves defining `operator[]` on the iterator, which indexes
at an offset: `i[n] == *(i + n)`.

Unfortunately I can't do the same for the _result_ iterator.  Way back
when, unaware of this requirement, I defined array indexing on a result
iterator as `i[n] == (*i)[n]`.  It made some code a lot more convenient
and seemed very logical.
jtv added a commit that referenced this issue Jul 5, 2024
Fixes: #846

This involves defining `operator[]` on the iterator, which indexes
at an offset: `i[n] == *(i + n)`.

Unfortunately I can't do the same for the _result_ iterator.  Way back
when, unaware of this requirement, I defined array indexing on a result
iterator as `i[n] == (*i)[n]`.  It made some code a lot more convenient
and seemed very logical.
@jtv
Copy link
Owner

jtv commented Jul 5, 2024

Tempting: it now looks like a "divorce" between result::const_iterator::operator[] and row::operator[] probably wouldn't break a lot of code!

I tried commenting out the operator[] on the iterator, and the only code that failed to compile was in tests specifically meant to exercise this kind of thing. The "natural" code I'd written in other tests remained unaffected.

So loath as I am to break compatibility like this, I probably could implement random_access_iterator in libpqxx 8.0.

(For now though, going with the demotion to bidirectional_iterator.)

@jtv jtv closed this as completed in #860 Jul 5, 2024
@jtv jtv closed this as completed in 020160b Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants