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

Alternative iterator interface #126

Merged
merged 11 commits into from
Feb 5, 2018
Merged

Conversation

zauguin
Copy link
Collaborator

@zauguin zauguin commented May 24, 2017

I think this feels more natural than the interface from #103 and it makes lifetime questions easier.
The database_binder behaves like a container now:

for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 21) {
  int _age;
  string _name;
  double _weight;
  row >> _age >> _name >> _weight;
  cout << _age << ' ' << _name << ' ' << _weight << endl;
}
// or
for(std::tuple<int, string, double> row : db << "select age,name,weight from user where age > ? ;" << 21) {
  cout << std::get<int>(row) << ' ' << std::get<string>(row) << ' ' << std::get<double>(row) << endl;
}

@Killili What do you think? It's not our usual stream-op interface, but I think this makes it clearer that the range doesn't exists independent from the statement.

@Killili
Copy link
Collaborator

Killili commented May 24, 2017

I like it more then #103 it looks more consistent with the rest of the API.

But now that i think about it, isnt it a lot like the prepared statement stuff?

auto prep = db << "select... where ?"; // create
prep << 21; // bind
auto itr = prep.begin() // "execute" // till here the prep statement does the same
auto row = itr++; // iterate

Maybe there is some synergy that could be used?

@zauguin zauguin changed the title [RFC] Alternative iterator interface Alternative iterator interface Jan 21, 2018
@zauguin
Copy link
Collaborator Author

zauguin commented Jan 21, 2018

I added some documentation and rebased it on the current master.
The old code for executing prepared statements is now internally implemented by using this interface.

If noone objects I would merge it soon.

@Killili
Copy link
Collaborator

Killili commented Jan 22, 2018

I'm totaly out the loop here. Didnt touch C++ for the last 10+ Month. So this look like magic now ;)
So i can only rely on my past selfs comment up there and he liked it. So great work bringing it to this point. But someone else should take a look. @aminroosta

}
template<class ...Types>
operator std::tuple<Types...>() {
std::tuple<Types...> value;
Copy link
Collaborator

Choose a reason for hiding this comment

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

@zauguin This constructs an empty tuple<Types...> and then assigns to each tuple elements.
Can we avoid the overhead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The problem is that our interface always gets a referrence to a preexisting object.
So fixing this would require to rewrite get_col_from_db for every type to return the value instead of storing it in a parameter.
This is a streight forward conversion but I think it would be better to do this in a separate Pull Request because it would add another 100+ line of changes, so reviewing this together could easiliy become be a pain.

Also the overhead should be small or nonexistent: The values are default constructed, then in a probably inlined function a value is assigned, so most optimizers should eliminate the additional construction anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I greed :-) 👍

tuple_iterate<std::tuple<Types...>>::iterate(values, *this);
});
// Not well-defined
row_iterator operator++(int);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@zauguin I think we should not declare the postfix operator, a compile error is better than a linker one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@aminroosta This was added to be compatible with the Ranges TS.
The operator++ will normally not be necessary, but it is checked for with concepts.
But this was a hack in the first place and I agree moving a compile-time to a link-time error is bad.

We can either just take it out. This is the nice way and we don't lie about our type. The downside would themissing compatibility with Ranges-v3 or the Ranges TS.

We could also rewrite it to fail at compile time iff we are in an evaluated context, so using it would lead to a compile-time error, but checking for it would show it's existance.
Obviously this would really be a ugly hack, but we could interact with most libraries expecting "real iterators".

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can either just take it out. This is the nice way and we don't lie about our type. The downside would themissing compatibility with Ranges-v3 or the Ranges TS.

I think we should do that, unless we can come up with so called "real iterators" :-)

typedef utility::function_traits<Function> traits;
private:
database_binder *_binder = nullptr;
mutable value_type value{_binder}; // mutable, because `changing` the value is just reading it
Copy link
Collaborator

@aminroosta aminroosta Jan 23, 2018

Choose a reason for hiding this comment

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

@zauguin I'm just wondering since we have no const member function modifying the value, why do we need mark it as mutable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If this is not mutable, operator* has to return a const reference to value_type instead.
But reading from the value_type requires increasing the index for the next column, next_index.

I see four ways to solve this:

  1. Make operator * and operator -> not const.
    This breaks requirements for iterators and might break code. IMO it would also be weird, because these functions are not changing anything.
  2. Make reference the same as value_type and return a new value_type at every call to the operators.
    If this is ever used with a not range for loop, everytime *iter is used the read restarts at the first column.
    So this could lead to very hard to find bugs.
  3. Make the value_type member functions const and value_type::next_index mutable. Then a const reference can be returned.
    This would work, but then we work const qualified instances of value_type, which don't act like const objects at all because they change if any function on them is called.
  4. The current approach: A mutable value.
    This is certainly not perfect, but it moves the problems of 3. to the iterator type which is less visible to the user.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@zauguin Thanks for the clarification, Since value_type does not own the underlying data, I too think solution No 4 is the right choice here.

Right now value_type will only read data if dereferenced (*value) or when it is implicitly converted to std::tuple, basically a non-owing type.

What are your thoughts if we make it own the entire row?
value_type can own an std::tuple member, or drive from std::tuple<Types...>
We will initialize & update the underlying tuple in row_iterator::operator++.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@zauguin If we drive from an std::tuple, can we take advantage of C++17 structured bindings?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@aminroosta This sounds like the interface of #103.
The fundamental problem is determining the type and number of columns at compile time. If the value_type should own the values, the value_type has to know about <Types...>, so the types have to be passed as explicit template arguments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@aminroosta How do you prefere to move forward here? Do you prefer the interface opions discussed in #103 or should we continue with this one?

@aminroosta
Copy link
Collaborator

aminroosta commented Jan 23, 2018

@zauguin Thanks for the really great work, This PR looks really interesting.
Sorry for the late reply, I missed the github notification.

I've added a review, But I think it looks good to merge even in its current shape.

@aminroosta
Copy link
Collaborator

@zauguin A quick discussion about the branches we are using.

I will propose, we should have a dev branch,
This PR, also the PR I had for blob_t could go to that branch.
It will effectively be the branch for the next release.
We will mention it in the master readme.

This way can have an experimental(dev) & stable(master) branch.

@zauguin
Copy link
Collaborator Author

zauguin commented Jan 23, 2018

@aminroosta This sounds like a great plan. Will you create the branch and set everything up?

@aminroosta
Copy link
Collaborator

aminroosta commented Jan 24, 2018

@zauguin Great, I will try to setup that by the end of this week.

I am planning to do the following:

  • Replace autotools with Cmake & add Catch.
  • Fix broken travis builds, happened after migrating to an organization.
  • Remove boost support.
  • Migrate our tests to use Catch.

@aminroosta
Copy link
Collaborator

Here is the WIP https://github.com/SqliteModernCpp/sqlite_modern_cpp/compare/master...aminroosta:dev?expand=1

@aminroosta
Copy link
Collaborator

@Killili
I have removed my old branches from the repo.
(except blob_t, which I will remove after merging #125).

There are 3 branches from you too (fix-138, nulltype, custom_db), could you please have a look.
Is it ok if we remove them? since we want to only keep master & dev from now on.

@zauguin zauguin changed the base branch from master to dev February 4, 2018 10:57
@Killili
Copy link
Collaborator

Killili commented Feb 5, 2018

@aminroosta i'm not sure what these branches do, so they are probably not important.

@aminroosta
Copy link
Collaborator

@Killili thanks, I deleted all of them.

@zauguin There is a merge conflict, please resolve this so we can merge this PR.
Thanks.

zauguin added a commit to zauguin/sqlite_modern_cpp that referenced this pull request Feb 5, 2018
@zauguin zauguin merged commit 65544ca into SqliteModernCpp:dev Feb 5, 2018
@zauguin zauguin mentioned this pull request Feb 7, 2018
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.

3 participants