2

EDIT: I have a working solution now, https://wandbox.org/permlink/joS14MzfmmayNRd3, just want to know if it can be improved.

I have a map map<int, unique_ptr<Base>>, whose values can also be of type unique_ptr<Derived>. I have wrapper classes A and B which store Base and Derived in the map respectively (all Base or all Derived instances).

I want to introduce map semantics (reference binding) for A and B that works as follows:

// a is of type A, all values in map are of type Base
for (auto& [k, v] : a) {
  // v should be of type Base&
} 

// b is of type B, all values in map are type Derived
for (auto& [k, v] : b) { 
  // v should be of type Derived&
  // can call functions that are in Derived but not in Base
} 

I found boost's transform_iterator and iterator_adaptor that could potentially be useful (even better if there is a way to do it without boost), but I don't seem to be using them correctly. What is the right way to do this?

#include <memory>
#include <boost/iterator/transform_iterator.hpp>
#include <vector>
#include <unordered_map>
#include <iostream>

using namespace std;

class Base {
    public:
        Base(int k) : i(k) {}
        virtual ~Base() {}
        
        virtual int getV() {
            return i;
        }
        
        int i = 0;
};

class Derived : public Base {
    public:
        Derived(int k) : Base(k) {}
        
        int getV() override {
            return j - i;
        }

        void setV(int p) {
            i = p;
        }
        
        int j = 0;
};

typedef unordered_map<int, unique_ptr<Base>> BaseMap;

class A {
    public:
        A(vector<int> keys) {
            for (auto& k : keys) {
                m.emplace(k, make_unique<Base>(k));
            }
        }
        
        class Converter {
            public:
                explicit Converter() {}

                pair<BaseMap::key_type, reference_wrapper<Base>> operator()(BaseMap::value_type& p) const {
                    return {p.first, *p.second};
                }
        };

        using MyIterator = boost::transform_iterator<Converter, typename BaseMap::iterator>;

        MyIterator begin() {
            return MyIterator(m.begin());
        }
        
        MyIterator end() {
            return MyIterator(m.end());
        }
       
        protected:
            BaseMap m;
};

class B : public A {
    public:
        B(vector<int> keys) : A(keys) {
            m.clear(); // demo only, since we have to init A
            for (auto& k : keys) {
                m.emplace(k, make_unique<Derived>(k));
            }
        }

        class Converter {
            public:
                explicit Converter() {}

                pair<BaseMap::key_type, reference_wrapper<Derived>> operator()(BaseMap::value_type& p) const {
                    return {p.first, dynamic_cast<Derived&>(*p.second)};
                }
        };
        
        using MyIterator = boost::transform_iterator<Converter, typename BaseMap::iterator>;
        
        MyIterator begin() {
            return MyIterator(m.begin());
        }
        
        MyIterator end() {
            return MyIterator(m.end());
        }
};

int main ()
{
    A a({1,2,3,4});
    B b({1,2,3,4});
    
    for (auto [k, v] : a) {
        cout << v.get().getV() << " ";
    }
    cout << endl;
    
    for (auto [k, v] : b) {
        cout << v.get().getV() << " ";
        v.get().setV(42);
    }
    
    cout << endl << "after setV:\n";
    
    for (auto [k, v] : b) {
        cout << v.get().getV() << " ";
    }
    
    return 0;
}
17andLearning
  • 477
  • 1
  • 8
  • 19
  • 1
    `can call functions that are in Derived but not in Base` - this goes against the very principles of object oriented programming and is going to cause you a lot of trouble (assuming `public` inheritance as shown in your code) – alter_igel Jun 17 '21 at 04:42
  • Did you know https://codereview.stackexchange.com/ ? – xtofl Jun 17 '21 at 05:35
  • 1
    @xtofl, nope, TIL -- asked a question on SO after a few years, haha – 17andLearning Jun 17 '21 at 05:45

1 Answers1

3

There are several problems here:

  1. You're taking p by value, that is, you're making a copy. But std::unique_ptr is not copyable.
  2. BaseMap::value_type is already an std::pair<const Key, Value>.
  3. std::make_pair() decays arguments, you need to wrap an argument into std::reference_wrapper with std::ref() to pass a reference.
class Converter {
public:
    std::pair<BaseMap::key_type, Derived&> operator()(BaseMap::value_type& p) const {
        return std::make_pair(p.first, std::ref(dynamic_cast<Derived&>(*p.second)));
    }
};

After that, a range-based for should look like this:

for (auto [k, v] : b) {
    std::cout << v.get();
}

Note that decltype(v) is Derived&, not Derived.

(This answer was written before code in the question was edited.)

Evg
  • 25,259
  • 5
  • 41
  • 83
  • Thanks! As you probably realized, I edited the code and have a working solution: https://wandbox.org/permlink/8CHA1Xwn604zVg7P Wondering if there is any way to improve on it. Specifically, calling .get() on the reference wrapper is a not too developer friendly – 17andLearning Jun 17 '21 at 05:36
  • @17andLearning Why do you need `get()`? This call is redundant. `*p.second` returns a reference to the stored object. – Evg Jun 17 '21 at 05:46
  • Oh, I meant while traversing over the map, the `get()` to get the reference out of the std::reference_wrapper. A and B are being exposed as an API, so would like to emulate map semantics as much as possible – 17andLearning Jun 17 '21 at 05:47
  • 1
    There is no need to return a `reference_wrapper` in the pair. See my example. For a range-based `for`, use `auto`. – Evg Jun 17 '21 at 05:51
  • Perfect. Thank you for saving me so much time, Internet stranger! :) – 17andLearning Jun 17 '21 at 05:54