Skip to content

Range for loop for statements#545

Open
Alvov1 wants to merge 8 commits into
SRombauts:masterfrom
Alvov1:181-feature-statement-iterator
Open

Range for loop for statements#545
Alvov1 wants to merge 8 commits into
SRombauts:masterfrom
Alvov1:181-feature-statement-iterator

Conversation

@Alvov1

@Alvov1 Alvov1 commented Jun 1, 2026

Copy link
Copy Markdown

Closes #181

Adds a nested RowIterator class to Statement that enables range-based for loops over query results:

SQLite::Statement query(db, "SELECT id, name FROM test");
for (SQLite::Statement& row : query)
{
    std::cout << row.getColumn(0).getInt() << "\n";
}

Notes:

  • begin() calls reset() automatically, so re-iterating the same Statement always starts from the beginning
  • Bindings set before the loop are preserved across reset()
  • Empty result sets are handled correctly (loop body never executes)
  • RowIterator satisfies std::input_iterator_tag with full iterator traits

@Alvov1 Alvov1 force-pushed the 181-feature-statement-iterator branch from fbb99e8 to 83bb827 Compare June 2, 2026 06:32
@SRombauts SRombauts self-assigned this Jun 2, 2026
Comment thread include/SQLiteCpp/Statement.h
Comment thread include/SQLiteCpp/Statement.h Outdated
Comment thread src/Statement.cpp
@Alvov1 Alvov1 force-pushed the 181-feature-statement-iterator branch from 300d023 to c996909 Compare June 12, 2026 20:17

@SRombauts SRombauts left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, all three earlier points are addressed. Two small follow-ups below, both non-blocking.

/// Post-increment: advance to the next row, return a copy of the iterator before advancing.
SQLITECPP_API RowIterator operator++(int);

/// Return true when two iterators point to the same row.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is fine, but the doc overstates it. It compares the Statement*, not the row, so two live iterators over the same statement always compare equal regardless of position. That is correct for a single-pass stream iterator (it is how std::istream_iterator works, and your @warning already rules out two live iterators). Only the wording needs to match:

/// Return true when both iterators refer to the same statement, or are both the end sentinel.

Comment thread src/Statement.cpp
return *this;
}

Statement::RowIterator Statement::RowIterator::operator++(int)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth flagging: the copy this returns shares the one live cursor, so *it++ gives the row after the increment, not before. This is inherent, not a bug to chase down. operator* hands back the single Statement&, Statement is move-only (Statement.h:79), and sqlite overwrites the row data on the next step, so the returned copy cannot hold the old row. std::istream_iterator gets away with *it++ because it caches its value by copy; here the value is a live cursor, so we cannot.

Caching the row would mean copying every column into the iterator, which is not worth it. The honest signal for a single-pass iterator is to return void from post-increment:

void RowIterator::operator++(int) { ++*this; }

That still satisfies std::input_iterator (weakly_incrementable only requires i++ to be a valid expression, with no constraint on its return type), and it turns auto old = *it++; into a compile error instead of a silent wrong-row read. If you would rather keep the conventional copy-returning form, add a @warning saying the iterator is single-pass and *it++ reflects the advanced row.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct, a good catch
Returning a void, and producing a compile error is much safer than making a warning that could be missed

@SRombauts

SRombauts commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Hey @Alvov1, thanks a lot for your contribution!
I was going to approve and merge it as-is, but I discovered the subtleties of the operation++() return value (see review comment above).
It's not a big deal, but I'd like to know what you think of it: I would just return void if it was me, but you may have other ideas on how you would use it yourself
Cheers!
Sébastien

@SRombauts

Copy link
Copy Markdown
Owner

One more thought, and only if you are up for it: this feature is genuinely nice, and right now the only place it shows up is the tests. Would you be open to adding a small bit of documentation so people actually discover it? The natural spot is the query loop snippet in README.md (around line 341), where the classic while (query.executeStep()) example lives; showing the range-based for equivalent right next to it would do the job. A short variant in examples/example1/main.cpp would also work and has the bonus of being compiled by CI.

No worries if you would rather I add it after merging, just let me know which you prefer.

@Alvov1

Alvov1 commented Jun 28, 2026

Copy link
Copy Markdown
Author

Hi! I'll make it in a couple of days. Didn't forget

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 this pull request may close these issues.

Implement iterators and range-based for loops

2 participants