Converting a set of sets to a single range - c++

I'm having trouble with creating a certain type of range.
In LLVM (a compiler infrastructure), a Module (could be the code for a translation unit) is organized as follows:
llvm::Module m{...}; // A module could be a single translation unit.
for(const auto &func : m){
for(const auto &basic_block : func){
for(const auto &inst : basic_block){
}
}
}
In the code I'm working on, it's very convenient to have a function that creates a range of instructions contained in a Module or Function. For example:
llvm::Module m;
llvm::Function f;
InstSet temp;
temp.insert(instructions(m));
temp.insert(instructions(f));
However, I'm having trouble elegantly implementing the range.
Things I tried:
Implementing a custom iterator. It turned out to be really messy even though what I'm trying to do is supposed to be simple...
Implementing using coroutines. While this worked out slightly better, the performance was unacceptable (about 10x slower upon investigation).
My question is, are there any elegant solutions that I'm missing? If there is none, I'd like to know why to understand the problem I'm trying to solve the wrong way.
EDIT (I added my own implementations below):
Here is the implementation that uses coroutines (finished):
struct InstructionsGenerator {
struct promise_type;
using Handle = coroutine_handle<promise_type>;
struct promise_type {
const Instruction *i;
auto get_return_object() -> InstructionsGenerator { return {Handle::from_promise(*this)}; }
auto yield_value(const Instruction &y) -> suspend_always {
i = &y;
return {};
}
auto initial_suspend() -> suspend_never { return {}; };
auto final_suspend() -> suspend_always { return {}; };
auto return_void() -> void {}
};
Handle h;
};
auto instructions_generator(const Module &m) -> InstructionsGenerator {
for (const auto &f : m) {
for (const auto &bb : f) {
for (const auto &i : instructions(bb)) {
co_yield i;
}
}
}
}
struct InstructionRange {
struct iterator {
optional<InstructionsGenerator> g;
auto operator++() -> auto & {
g->h();
return *this;
}
auto operator*() const -> auto & { return *(g->h.promise().i); }
auto operator==(const iterator & /*unused*/) const -> bool { return g->h.done(); }
};
iterator b;
[[nodiscard]] auto begin() const -> iterator { return b; }
[[nodiscard]] auto end() const -> iterator { return {nullopt}; };
~InstructionRange() {
if (b.g) {
b.g->h.destroy();
}
}
InstructionRange(InstructionsGenerator fi) : b{fi} {}
InstructionRange(InstructionRange &&r) noexcept : b{r.b} { r.b.g = nullopt; }
};
auto instructions(const Module &m) -> InstructionRange { return {instructions_generator(m)}; };
Here is the implementation that uses custom iterators (unfinished. Abandoned it after realizing it's too much of a hassle.):
// The real implementation was templated but to give you the idea, I've simplified it.
struct LiftedRange {
llvm::Module& m;
public:
struct iterator {
llvm::Module& m;
llvm::Module::iterator l1;
llvm::Function::iterator l2;
auto operator*(); // Skipped for brevity.
auto operator==(); // Skipped for brevity.
auto operator++(){
if(next(l2) != l1.end()){
++l2;
return *this;
}
do {
++l1;
while(l1->empty() || l1 != m.end()); // Avoid empty sets.
l2 = l1->begin();
return *this;
}
};
auto begin(); // Skipped for brevity.
auto end(); // Skipped for brevity.
};

Related

How to recursively yield generator by overloading yield_value?

I've created a generator that will have an overload operator* in order to be converted into std::ranges::subrange and I also want to overload yield_value from promise_type that accepts a subrange type that will be yielded recursively.
Source Code:
template <typename T>
class [[nodiscard]] generator {
public:
using value_type = T;
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
private:
handle_type handle_ { nullptr };
explicit generator(handle_type handle) : handle_(handle) {}
public:
struct promise_type {
value_type value_;
generator<value_type> get_return_object() {
return generator{ handle_type::from_promise(*this) };
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() { return {}; }
void unhandled_exception() { std::terminate(); }
std::suspend_always yield_value(const value_type& value) noexcept {
value_ = value;
return {};
}
template <typename U>
std::suspend_never await_transform(U&&) = delete;
void return_void() {}
};
generator() noexcept = default;
generator(const generator&) = delete;
generator(generator&& other) noexcept
: handle_(std::move(other.handle_)) {
other.handle_ = nullptr;
}
~generator() { if (handle_) handle_.destroy(); }
generator& operator=(const generator&) = delete;
generator& operator=(generator&& other) noexcept {
handle_ = std::move(other.handle_);
other.handle_ = nullptr;
return *this;
}
void swap(generator& other) noexcept {
using std::swap;
swap(handle_, other.handle_);
}
class iterator {
private:
handle_type handle_;
friend generator;
explicit iterator(handle_type handle) noexcept
: handle_(handle) {}
public:
using value_type = std::remove_cvref_t<T>;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
iterator() noexcept = default;
friend bool operator==(const iterator& iter, std::default_sentinel_t) noexcept {
return iter.handle_.done();
}
friend bool operator==(std::default_sentinel_t s, const iterator& iter) noexcept {
return (iter == s);
}
iterator& operator++() {
if (handle_.done()) handle_.promise().unhandled_exception();
handle_.resume();
return *this;
}
iterator operator++(int) {
auto temp = *this;
++*this;
return temp;
}
reference operator*() noexcept {
return handle_.promise().value_;
}
pointer operator->() noexcept {
return std::addressof(operator*());
}
};
iterator begin() noexcept {
if (handle_) {
handle_.resume();
if (handle_.done())
handle_.promise().unhandled_exception();
}
return iterator{handle_};
}
std::default_sentinel_t end() noexcept {
return std::default_sentinel;
}
};
Example:
auto generate_0(int n) -> generator<int> {
while (n != 0)
co_yield n--;
}
auto generate_1() -> generator<int> {
for (const auto& elem : generate_0(10)) {
co_yield elem;
}
}
generate_1 will work obviously but I want have the same output like the generate_1 that each element is co_yield-ed directly inside the yield_value:
auto generate_1() -> generator<int> {
co_yield* generate_0(10);
}
Such that:
In class generator:
auto operator*() {
return std::ranges::subrange(begin(), end());
}
In nested class generator<...>::promise_type:
template <typename U>
std::suspend_always yield_value(const std::ranges::subrange<U, std::default_sentinel_t>& r) noexcept {
/** ... **/
return {};
}
First things first: bugs/odd bits on your end.
I don't think it's worth it trying to support old-style iterators. It doesn't make sense to default-construct generator<T>::iterator, and the new-style iterator concepts do not require it. You can tear out a lot of junk from iterator.
Also, == is magical. If x == y doesn't find a matching operator== but y == x does, then x == y is automatically rewritten to y == x. You don't need to provide both operator==s.
The promise_type does not need to hold T by value. An odd thing about yielding things from coroutines is that if you make yield_value take by-reference, you can get a reference to something that lives in the coroutine state. But the coroutine state is preserved until you resume it! So promise_type can instead hold T const*. Now you no longer require annoying things like copyability and default-constructibility from T.
It appears to be unnatural for a generator to initially suspend. Currently, if you do g.begin(); g.begin();, you will advance the generator even though you've incremented no iterator. If you make g.begin() not resume the coroutine and remove the initital suspension, everything just works. Alternatively, you could make generator track whether it has started the coroutine and only advance it to the first yield on begin(), but that's complicated.
While calling std::terminate() on every operation that's normally UB may be nice, it's also noisy and I'm just not going to include it in this answer. Also, please don't call it via unhandled_exception. That's just confusing: unhandled_exception has one very specific purpose and meaning and you are just not respecting that.
generator<T>::operator=(generator&&) leaks *this's coroutine state! Also, your swap is nonstandard because it is not a free 2-arg function. We can fix these by making operator= do what swap did and then getting rid of swap because std::swap works.
From a design/theory standpoint, I think it makes more sense to implement this syntax instead.
auto generate_1() -> generator<int> {
co_await generate_0(10);
}
A generator can temporarily give up control to another and may resume running after it awaits for the inner generator to run out. Implementing something to yield from a range can be easily implemented atop this by making a generator wrapping the range. This also lines up with the syntax in other languages like Haskell.
Now, coroutines have no stack. That means that as soon as we cross a function call boundary away from a coroutine like generate_1, it is not possible to suspend/resume that function via the coroutine state associated with the caller. So we have to implement our own stack, where we extend our coroutine state (promise_type) with the ability to record that it is currently pulling from another coroutine instead of having its own value. (Please note this would also apply to yielding from a range: whatever function is called to receive the range from generator_1 will not be able to control generator_1's coroutine.) We do this by making promise_type hold a
std::variant<T const*, std::subrange<iterator, std::default_sentinel_t>> value;
Note that promise_type does not own the generator represented by the subrange. Most of the time (as it is in generator_1) the same trick as yield_value applies: the generator which owns the sub-coroutine's state lives inside the caller coroutine's stack.
(This is also a point against directly implementing co_yield from a range: we need to fix the type of whatever is going into promise_type. From an API standpoint, it's understandable for co_await inside a generator<T> to accept generator<T>s. But if we implemented co_yield we'd only be able to directly handle one specific kind of rangeā€”a subrange wrapping a generator. That'd be weird. And to do otherwise we'd need to implement type-erasure; but the most obvious way to type-erase a range in this context is to make a generator. So we're back to a generator awaiting on another as being the more fundamental operation.)
The stack of running generators is now a linked-list threaded through their promise_types. Everything else just writes itself.
struct suspend_maybe { // just a general-purpose helper
bool ready;
explicit suspend_maybe(bool ready) : ready(ready) { }
bool await_ready() const noexcept { return ready; }
void await_suspend(std::coroutine_handle<>) const noexcept { }
void await_resume() const noexcept { }
};
template<typename T>
class [[nodiscard]] generator {
public:
struct iterator;
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
using range_type = std::ranges::subrange<iterator, std::default_sentinel_t>;
private:
handle_type handle;
explicit generator(handle_type handle) : handle(std::move(handle)) { }
public:
class iterator {
private:
handle_type handle;
friend generator;
explicit iterator(handle_type handle) noexcept : handle(handle) { }
public:
// less clutter
using iterator_concept = std::input_iterator_tag;
using value_type = std::remove_cvref_t<T>;
using difference_type = std::ptrdiff_t;
// just need the one
bool operator==(std::default_sentinel_t) const noexcept {
return handle.done();
}
// need to muck around inside promise_type for this, so the definition is pulled out to break the cycle
inline iterator &operator++();
void operator++(int) { operator++(); }
// again, need to see into promise_type
inline T const *operator->() const noexcept;
T const &operator*() const noexcept {
return *operator->();
}
};
iterator begin() noexcept {
return iterator{handle};
}
std::default_sentinel_t end() const noexcept {
return std::default_sentinel;
}
struct promise_type {
// invariant: whenever the coroutine is non-finally suspended, this is nonempty
// either the T const* is nonnull or the range_type is nonempty
// note that neither of these own the data (T object or generator)
// the coroutine's suspended state is often the actual owner
std::variant<T const*, range_type> value = nullptr;
generator get_return_object() {
return generator(handle_type::from_promise(*this));
}
// initially suspending does not play nice with the conventional asymmetry between begin() and end()
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
std::suspend_always yield_value(T const &x) noexcept {
value = std::addressof(x);
return {};
}
suspend_maybe await_transform(generator &&source) noexcept {
range_type range(source);
value = range;
return suspend_maybe(range.empty());
}
void return_void() { }
};
generator(generator const&) = delete;
generator(generator &&other) noexcept : handle(std::move(other.handle)) {
other.handle = nullptr;
}
~generator() { if(handle) handle.destroy(); }
generator& operator=(generator const&) = delete;
generator& operator=(generator &&other) noexcept {
// idiom: implementing assignment by swapping means the impending destruction/reuse of other implicitly handles cleanup of the resource being thrown away (which originated in *this)
std::swap(handle, other.handle);
return *this;
}
};
// these are both recursive because I can't be bothered otherwise
// feel free to change that if it actually bites
template<typename T>
inline auto generator<T>::iterator::operator++() -> iterator& {
struct visitor {
handle_type handle;
void operator()(T const*) { handle(); }
void operator()(range_type &r) {
if(r.advance(1).empty()) handle();
}
};
std::visit(visitor(handle), handle.promise().value);
return *this;
}
template<typename T>
inline auto generator<T>::iterator::operator->() const noexcept -> T const* {
struct visitor {
T const *operator()(T const *x) { return x; }
T const *operator()(range_type &r) {
return r.begin().operator->();
}
};
return std::visit(visitor(), handle.promise().value);
}
Nothing appears to be on fire.
static_assert(std::ranges::input_range<generator<unsigned>>); // you really don't need all that junk in iterator!
generator<unsigned> generate_0(unsigned n) {
while(n != 0) co_yield n--;
}
generator<unsigned> generate_1(unsigned n) {
co_yield 0;
co_await generate_0(n);
co_yield 0;
}
int main() {
auto g = generate_1(5);
for(auto i : g) std::cout << i << "\n"; // 0 5 4 3 2 1 0 as expected
// even better, asan is happy!
}
If you want to yield values from an arbitrary range, I would just implement this type-eraser.
auto generate_all(std::ranges::input_range auto &&r) -> generator<std::ranges::range_value_t<decltype(r)>> {
for(auto &&x : std::forward<decltype(r)>(r)) co_yield std::forward<decltype(x)>(x);
}
So you get e.g.
generator<unsigned> generate_1(unsigned n) {
co_await generate_all(std::array{41u, 42u, 43u});
co_await generate_0(n);
co_yield 0;
}

C++ coroutines: implementing task<void>

So, there I'm trying to comprehend that new & complex concept of the coroutines. Took Clang for it, compiling via clang++ -std=c++17 -fcoroutines-ts -stdlib=libc++ goes fine.
One of the most useful concepts is task<> coroutine type, it's mentioned here and even has a couple interesting implementations, by Gor Nishanov and in cppcoro library.
Okay, looked fine to try myself in the simpliest case. So, the goal is to implement something that should work like the following:
{
auto producer = []() -> task<int> {
co_return 1;
};
auto t = producer();
assert(!t.await_ready());
assert(t.result() == 1);
assert(t.await_ready());
}
The template class task<> itself was made quite straightforward:
#pragma once
#include <experimental/coroutine>
#include <optional>
namespace stdx = std::experimental;
template <typename T=void>
struct task
{
template<typename U>
struct task_promise;
using promise_type = task_promise<T>;
using handle_type = stdx::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{}
task(task&& other) noexcept
: m_handle(other.m_handle)
{ other.m_handle = nullptr; };
bool await_ready()
{ return m_handle.done(); }
bool await_suspend(stdx::coroutine_handle<> handle)
{
if (!m_handle.done()) {
m_handle.resume();
}
return false;
}
auto await_resume()
{ return result(); }
T result() const
{
if (!m_handle.done())
m_handle.resume();
if (m_handle.promise().m_exception)
std::rethrow_exception(m_handle.promise().m_exception);
return *m_handle.promise().m_value;
}
~task()
{
if (m_handle)
m_handle.destroy();
}
template<typename U>
struct task_promise
{
std::optional<T> m_value {};
std::exception_ptr m_exception = nullptr;
auto initial_suspend()
{ return stdx::suspend_always{}; }
auto final_suspend()
{ return stdx::suspend_always{}; }
auto return_value(T t)
{
m_value = t;
return stdx::suspend_always{};
}
task<T> get_return_object()
{ return {handle_type::from_promise(*this)}; }
void unhandled_exception()
{ m_exception = std::current_exception(); }
void rethrow_if_unhandled_exception()
{
if (m_exception)
std::rethrow_exception(std::move(m_exception));
}
};
};
Couldn't really make a smaller piece of code complete and compilable, sorry. Anyway it worked somehow, but there still remained the case of task<void>, it's usage could be like the following:
{
int result = 0;
auto int_producer = []() -> task<int> {
co_return 1;
};
auto awaiter = [&]() -> task<> { // here problems begin
auto i1 = co_await int_producer();
auto i2 = co_await int_producer();
result = i1 + i2;
};
auto t = awaiter();
assert(!t.await_ready());
t.await_resume();
assert(result == 2);
}
The latter didn't seem a problem at all, it looked like task_promise<U> required a specialization for void (could be a non-template struct without that void case). So, I tried it:
template<>
struct task_promise<void>
{
std::exception_ptr m_exception;
void return_void() noexcept {}
task<void> get_return_object() noexcept
{ return {handle_type::from_promise(*this)}; }
void unhandled_exception()
{ m_exception = std::current_exception(); }
auto initial_suspend()
{ return stdx::suspend_always{}; }
auto final_suspend()
{ return stdx::suspend_always{}; }
};
Neat and simple... and it causes segfault without any readable stacktrace =(
Works fine when task<> is changed to any non-void template, like task<char>.
What is wrong with my template specialization? Or am I missing some tricky concept with those coroutines?
Would be grateful for any ideas.
Apparently, the usual suspect was the criminal: specialization! From the standard itself [temp.expl.spec]/7
When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.
To avoid issue, let's make it as simple as possible: task_promise can be a non template and the member specialization is declared as soon as possible:
template<class T=void>
struct task{
//...
struct task_promise{
//...
};
};
//member specialization declared before instantiation of task<void>;
template<>
struct task<void>::task_promise{
//...
};

range-based for loop for private map values

I have the following code:
#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
RETURNTYPE GetStringIterator() const
{
IMPLEMENTATION
}
private:
std::map<int, std::string> m_Items;
};
int main()
{
MyObject o;
for (auto& s : o.GetStringIterator())
{
std::cout << s;
}
}
What should RETURNTYPE and IMPLEMENTATION be in order to allow any client of MyObject (in this case the main() function), to iterate over the values of the m_Items map, without copying any data? It seems that this should be possible with c++11 range based for loops and iterators. but I have not been able to figure out how.
range-based iteration can be achieved like this:
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
auto begin() { return m_Items.begin(); }
auto begin() const { return m_Items.begin(); }
auto end() { return m_Items.end(); }
auto end() const { return m_Items.end(); }
private:
std::map<int, std::string> m_Items;
};
Copying or not copying the value depends on how the code is written at the call site:
MyObject a;
for(auto [key,value] : a) {} // copies are made
for(auto & [key,value] : a) {} // no copy
for(auto const & [key,value] : a) {} // no copy
And you can disable the modification of map values by removing the non-const versions of begin and end :
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
auto begin() const { return m_Items.begin(); }
auto end() const { return m_Items.end(); }
private:
std::map<int, std::string> m_Items;
};
Then, attempts at modifying the value in a range-for loop will lead to a compilation error:
MyObject a;
for(auto & [key,value] : a) {
//value.push_back('a'); // Not OK
}
for(auto & [key,value] : a) {
cout << value; // OK
}
Note that if the map is an implementation detail, the answer proposed by #Barry should be used, because it iterates only on the values of the map, not on the keys too.
You could use boost::adaptors::map_values, it works in C++11:
auto GetStringIterator() const
// NB: have the move the declaration of m_Items ahead of this function for this to work
-> decltype(m_Items | boost::adaptors::map_values)
{
return m_Items | boost::adaptors::map_values;
}
Or its range-v3 equivalent, view::values. Both can be used like values(m) instead of m | values, if you prefer it that way.
Either solution returns a view onto the values of the map. This is an object that doesn't own any of its underlying elements and is cheap to copy - that is, O(1). We're not coyping the map, or any of its underlying elements.
You would use this as if it were any other range:
for (std::string const& s : o.GetStringIterator()) {
// ...
}
This loop does not copy any strings. Each s refers directly into the corresponding string that the map is storing.
I'm going to first answer this in c++14.
Here is a minimal mapping iteratoroid:
template<class F, class It>
struct iterator_mapped {
decltype(auto) operator*() const {
return f(*it);
}
iterator_mapped( F f_in, It it_in ):
f(std::move(f_in)),
it(std::move(it_in))
{}
iterator_mapped( iterator_mapped const& ) = default;
iterator_mapped( iterator_mapped && ) = default;
iterator_mapped& operator=( iterator_mapped const& ) = default;
iterator_mapped& operator=( iterator_mapped && ) = default;
iterator_mapped& operator++() {
++it;
return *this;
}
iterator_mapped operator++(int) {
auto copy = *this;
++*this;
return copy;
}
friend bool operator==( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
return lhs.it == rhs.it;
}
friend bool operator!=( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
return !(lhs==rhs);
}
private:
F f;
It it;
};
it is not technically an iterator, but it qualifies for for(:) loops.
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
};
template<class It>
range_t<It> range( It b, It e ) {
return {std::move(b), std::move(e)};
}
the above is an absolutely minimal iterator range type that can be for(:) iterated.
template<class F, class R>
auto map_range( F&& f, R& r ) {
using std::begin; using std::end;
auto b = begin(r);
auto e = end(r);
using it = iterator_mapped<std::decay_t<F>, decltype(b)>;
return range( it( f, b ), it( f, e ) );
}
note that R& not R&&; taking an rvalue for r here is dangerous.
auto GetStringIterator() const
{
return map_range( [](auto&& pair)->decltype(auto){
return pair.second;
}, m_Items );
}
and done.
Converting this to c++11 is a pain. You have to toss around std::functions in place of lambdas (or write function objects that do the task instead of a lambda), replace decltype(auto) with auto and trailing return types, give the exact type of auto&& arguments to lambdas, etc. You end up with about 25%-50% more code, most of it obscure type chasing.
This is basically what boost::adaptors::map_values does, but this is hand-rolled so you can understand how it works and don't have a boost dependency.

Using class member variable as reference

I would like to have a class member variable to be able to switch in between items in a map so that when it is modified, the content of the map is also modified.
Is there any way other than to use a pointer to the content of the map ? Old code only needed only variable, now the new one needs to switch. If I change the variable type, then all functions using this member variable need to be changed. Not complicated, but I would find it ugly to have * in front of it everywhere...
A reference variable cannot be rebound, so how can I achieve this ?
class A
{
std::map<std::string,std::vector<int>> mMyMap;
std::vector<int>& mCurrentVector;
std::vector<int>* mCurrentVectorPointer;
std::vector<int> mDefaultVector;
void setCurrentVector(int iKey);
void addToCurrentVector(int iValue);
}
A::A():
mDefaultVector(std::vector<int>())
mCurrentVector(mDefaultVector)
{
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
mCurrentVectorPointer = &mMyMap[0];
}
A::setCurrentVector(std::string iKey)
{
if(mMyMap.find(iKey) != mMyMap.end())
{
mCurrentVector = mMyMap[iKey]; //can't change a reference...
mCurrentVectorPointer = &mMyMap[iKey]; //could use pointer, but
}
}
A::addToCurrentVector(int iValue)
{
mCurrentVector.push_back(iValue);
//or
(*mCurrentVectorPointer).push_back(iValue);
//
mCurrentVectorPointer->push_back(iValue);
}
void main()
{
A wClassA();
wClassA.setCurrentVector("key2");
wClassA.addToCurrentVector(3);
wClassA.setCurrentVector("key1");
wClassA.addToCurrentVector(4);
}
mMyMap["key1"] now contains 1,4
mMyMap["key2"] now contains 2,3
You can't reseat a reference once it has been assigned which means you are left with using your other option, a pointer.
As I understand it, you're refactoring some existing code which only used a single vector, whereas now you need a map of vectors.
You're trying to achieve this with minimal modifications, and keeping the interface to the vector the same.
An option would be to use a local reference, assigned from your pointer.
class A
{
using Vector = std::vector<int>;
public:
A()
{
map_["key1"] = std::vector<int>(1,1);
map_["key2"] = std::vector<int>(1,2);
curr_vec_ = &map_["key1"];
}
void setCurrentVector(const std::string& key)
{
if(map_.find(key) != map_.end())
{
curr_vec_ = &map_[key];
}
}
void addToCurrentVector(int val)
{
assert(curr_vec_);
Vector& curr_vec = *curr_vec_; // local reference
curr_vec.push_back(val);
curr_vec[0] = 2;
// etc
}
private:
std::map<std::string, Vector> map_;
Vector* curr_vec_ = nullptr;
}
You may write some wrapper:
#define Return(X) noexcept(noexcept(X)) -> decltype(X) { return X; }
template <typename U>
class MyVectorRef
{
private:
std::vector<U>* vec = nullptr;
public:
explicit MyVectorRef(std::vector<U>& v) : vec(&v) {}
void reset(std::vector<U>& v) {vec = &v;}
// vector interface
auto at(std::size_t i) const Return(vec->at(i))
auto at(std::size_t i) Return(vec->at(i))
auto operator [](std::size_t i) const Return(vec->operator[](i))
auto operator [](std::size_t i) Return(vec->operator[](i))
template <typename ... Ts> auto assign(Ts&&... ts) Return(vec->assign(std::forward<Ts>(ts)...))
auto assign( std::initializer_list<U> ilist ) Return(vec->assign(ilist))
template <typename T> auto push_back(T&& t) const Return(vec->push_back(std::forward<T>(t)))
template <typename T> auto emplace_back(T&& t) const Return(vec->emplace_back(std::forward<T>(t)))
auto begin() const Return(vec->begin())
auto begin() Return(vec->begin())
auto end() const Return(vec->end())
auto end() Return(vec->end())
auto cbegin() const Return(vec->cbegin())
auto cend() const Return(vec->cend())
// ...
};
and then, use it:
class A
{
public:
A() : mCurrentVector(mDefaultVector) {
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
}
std::map<std::string, std::vector<int>> mMyMap;
std::vector<int> mDefaultVector;
MyVectorRef<int> mCurrentVector;
void setCurrentVector(std::string iKey)
{
auto it = mMyMap.find(iKey);
if (it != mMyMap.end())
{
mCurrentVector.reset(it->second);
}
}
void addToCurrentVector(int iValue)
{
mCurrentVector.push_back(iValue);
}
};
But I think it would be simpler to just create a getter in A and use directly a pointer:
class A
{
public:
A() : mCurrentVector(&mDefaultVector) {
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
}
std::map<std::string, std::vector<int>> mMyMap;
std::vector<int> mDefaultVector;
std::vector<int>* mCurrentVector;
std::vector<int>& GeCurrentVector() { return *mCurrentVector; }
void setCurrentVector(std::string iKey)
{
auto it = mMyMap.find(iKey);
if (it != mMyMap.end())
{
mCurrentVector = &it->second;
}
}
void addToCurrentVector(int iValue)
{
GeCurrentVector().push_back(iValue);
}
};

Iterator for multi-dimensional vector that is used as unidimensional?

I have a vector that looks like this:
std::vector<std::vector<MyClass>> myVector;
And I would like to access its elements through iterators as if it was an unidimensional vector:
for (auto& x : myVector)
{
foo(x); // x is an object of type MyClass
}
(i.e. the fact that there are multiple dimensions is transparent to whoever loops through myVector)
I have an idea of how this should be done, have a custom iterator implementation that saves current indexes so that when one of the vectors has no more elements, it resets one of the indexes and increments the other so that it can start iterating through the next vector and so on. But I have been trying to code this idea but can't seem to get this working. Does anyone have any idea of how I can possibly achieve this? Or even better, if there's any open-source project that has a similar implementation?
Thanks.
It's totally possible to define your own iterator to hide all the details of iterating through a vector of vector, I wrote some code to give you the idea, mind that it should require more checks but it basically works and give you the idea.
You just need to write the required operations to make it work in other code like an opaque iterator.
template <typename T>
struct vector2d
{
public:
class iterator
{
public:
using vector_type = std::vector<std::vector<T>>;
using first_level_iterator = typename std::vector<std::vector<T>>::iterator;
using second_level_iterator = typename std::vector<T>::iterator;
private:
vector_type& data;
first_level_iterator fit;
second_level_iterator sit;
public:
iterator(vector_type& data, bool begin) : data(data)
{
if (begin)
{
fit = data.begin();
sit = fit->begin();
}
else
{
fit = data.end();
}
}
inline bool operator!=(const iterator& other) const { return fit != other.fit || (fit != data.end() && sit != other.sit); }
inline const iterator& operator++() {
// don't go past end
if (fit == data.end())
return *this;
// increment inner iterator
++sit;
// if we reached the end of inner vector
if (sit == fit->end())
{
// go to next vector
++fit;
// if we reached end then don't reset sit since it would be UB
if (fit != data.end())
sit = fit->begin();
}
return *this;
}
T& operator*() const { return *sit; }
};
public:
std::vector<std::vector<T>> data;
iterator begin() { return iterator(this->data, true); }
iterator end() { return iterator(this->data, false); }
};
A small test:
int main() {
vector2d<int> data;
data.data.push_back(vector<int>());
data.data.push_back(vector<int>());
data.data.push_back(vector<int>());
for (int i = 1; i < 5; ++i)
{
data.data[0].push_back(i);
data.data[1].push_back(i*2);
data.data[2].push_back(i*3);
}
for (auto i : data)
{
cout << i << endl;
}
return 0;
}
The behavior is rather simple but you must make sure that it's always consistent for all the edge cases.
A pretty minimal range type:
template<class It>
struct range_t {
private:
It b, e;
public:
It begin() const { return b; }
It end() const { return e; }
decltype(auto) front() const { return *b; }
decltype(auto) back() const { return *std::prev(e); }
bool empty() const { return b==e; }
range_t without_front( std::size_t n = 1 ) const {
auto r = *this;
std::advance(r.b,n);
return r;
}
range_t without_back( std::size_t n = 1 ) const {
auto r = *this;
std::advance(r.e,std::ptrdiff_t(-n));
return r;
}
range_t(It s, It f):b(std::move(s)), e(std::move(f)) {}
range_t():b(), e() {}
};
template<class It>
range_t<It> range( It b, It e ) {
return {std::move(b), std::move(e)};
}
Doing this task is far easier with ranges than with iterators.
template<class Outer, class Inner>
struct stacked_range_t {
range_t<Outer> outer;
stacked_range_t()=default;
stacked_range_t( range_t<Outer> o ):outer(std::move(o)) {}
struct iterator {
private:
range_t<Outer> outer;
range_t<Inner> inner;
public:
iterator(
range_t<Outer> o,
range_t<Inner> i
):outer(std::move(o)), inner(std::move(i)) {}
iterator()=default;
friend auto mytie(iterator const& it) {
return std::tie( it.outer.begin(), it.inner.begin(), it.inner.end() );
}
friend bool operator==(iterator const& lhs, iterator const& rhs) {
return mytie(lhs)==mytie(rhs);
}
friend bool operator!=(iterator const& lhs, iterator const& rhs) {
return mytie(lhs)==mytie(rhs);
}
using difference_type = std::ptrdiff_t;
using value_type = typename std::iterator_traits<Inner>::value_type;
using pointer = typename std::iterator_traits<Inner>::pointer;
using reference = typename std::iterator_traits<Inner>::reference;
using iterator_category = std::input_iterator_tag;
reference operator*() const {
return *inner.begin();
}
pointer operator->() const {
return inner.begin().operator->();
}
iterator& operator++() {
using std::begin; using std::end;
inner = inner.without_front();
while (inner.empty())
{
outer = outer.without_front();
if (!outer.empty())
inner = range( begin(outer.front()), end(outer.front()) );
}
return *this;
}
iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
};
iterator end() const {
return { range( outer.end(), outer.end() ), {} };
}
// a bit tricky:
iterator begin() const {
if (outer.empty()) return end();
auto rout = outer;
while( !rout.empty() ) {
using std::begin; using std::end;
auto rin = range( begin(rout.front()), end(rout.front()) );
if (!rin.empty())
return {std::move(rout), std::move(rin)};
rout = rout.without_front();
}
return end();
}
};
and a function to create it:
template<class Range>
auto make_stacked_range(Range& r) {
using std::begin; using std::end;
using Outer = decltype(begin(r));
using Inner = decltype(begin(*begin(r));
return stacked_range_t<Outer, Inner>{
{begin(r), end(r)}
};
}
there probably are typos. Use of C++1z features can be worked around with overly annoying decltype expressions and helper traits classes.
Relies on the iterators being trivially constructible, and such trivially constructed iterators are equal.
try to use template recursion,e.g.:
#include <stdio.h>
#include <vector>
template <typename V>
void f(V& v){
for(auto& e : v){
f(e);
}
printf("\n");
}
template <>
void f(int& v){
printf("%d ",v);
}
int main(){
std::vector<int> v1={1,2};
f(v1);
std::vector<std::vector<int> > v2={{3,4},{5,6,7}};
f(v2);
return 0;
};
With range/v3:
for (auto& x : myVector | ranges::view::join)
{
foo(x); // x is an object of type MyClass&
}
Demo