Home > Blockchain >  How to dereference an arbitrary type of container iterator to a certain type in a template function
How to dereference an arbitrary type of container iterator to a certain type in a template function

Time:12-14

I want to create a function which takes any kind of container of a certain type and does something with it's elements. Thanks to this, I was able to write a function which takes iterators as templated arguments. However this solution only works for containers, which are directly dereferencable to that certain type like std::vector<SomeObject> or std::deque<SomeObject>. How do I make this function work with std::vector<std::shared_ptr<SomeObject>> or std::map<int, SomeObject> as well? My idea is to let the user provide how to dereference the iterator with a function parameter like std::function<const SomeObject& (Iter)>, where Iter is the template argument of the function.

This is my code:

#include <functional>
#include <iostream>
#include <string>
#include <map>
#include <memory>

class SomeObject {
public:
  SomeObject(int id, const std::string& name) : id(id), name(name) {
  }
  
  int getId() const {
    return id;
  }
  
  void doSomething() const {
    std::cout << name << std::endl;
  }
private:
  int id;
  std::string name;
};

template <typename Iter>
void doSomethingWithMultipleObjectsInAnArbitraryContainer(Iter begin, Iter end, std::function<const SomeObject& (Iter)> dereferenceIterToObject) {
  if (begin == end) {
    // The container is empty. Nothing to do...
    return;
  }

  for (Iter it = begin; it != end;   it) {
    const SomeObject& obj = dereferenceIterToObject(it);
    // Do something with the object...
    obj.doSomething();
  }
}

int main(int argc, char** argv) {
  SomeObject obj1{1, "one"};
  SomeObject obj2{2, "two"};
  SomeObject obj3{3, "three"};
  
  std::map<int, SomeObject> mapWithValues {
    {obj1.getId(), obj1},
    {obj2.getId(), obj2},
    {obj3.getId(), obj3}
  };
  std::map<int, std::shared_ptr<SomeObject>> mapWithPointers {
    {obj1.getId(), std::make_shared<SomeObject>(obj1)},
    {obj2.getId(), std::make_shared<SomeObject>(obj2)},
    {obj3.getId(), std::make_shared<SomeObject>(obj3)}
  };
  std::vector<SomeObject> vectorWithValues {
    obj1,
    obj2,
    obj3
  };
  
  doSomethingWithMultipleObjectsInAnArbitraryContainer(mapWithValues.cbegin(), mapWithValues.cend(), 
    [](std::map<int, SomeObject>::const_iterator it) -> const SomeObject& {
      return it->second;
  }); // Compilation error here
  
  doSomethingWithMultipleObjectsInAnArbitraryContainer(mapWithPointers.cbegin(), mapWithPointers.cend(), 
    [](std::map<int, std::shared_ptr<SomeObject>>::const_iterator it) -> const SomeObject& {
      return *it->second;
  }); // Compilation error here
  
  doSomethingWithMultipleObjectsInAnArbitraryContainer(vectorWithValues.cbegin(), vectorWithValues.cend(), 
    [](std::vector<SomeObject>::const_iterator it) -> const SomeObject& {
      return *it;
  }); // Compilation error here
  
  return 0;
}

Unfortunately, this does not compile. This is the error:

error: no matching function for call to ‘doSomethingWithMultipleObjectsInAnArbitraryContainer(std::map<int, SomeObject>::const_iterator, std::map<int, SomeObject>::const_iterator, main(int, char**)::<lambda(std::map<int, SomeObject>::const_iterator)>)’ });

note: candidate: ‘template void doSomethingWithMultipleObjectsInAnArbitraryContainer(Iter, Iter, std::function<const SomeObject&(Iter)>)’ void doSomethingWithMultipleObjectsInAnArbitraryContainer(Iter begin, Iter end, std::function<const SomeObject& (Iter)> dereferenceIterToObject) {

note: template argument deduction/substitution failed: test_module/IecTestServer.cpp:131:4: note: ‘main(int, char**)::<lambda(std::map<int, SomeObject>::const_iterator)>’ is not derived from ‘std::function<const SomeObject&(Iter)>’ });

Can I fix this compilation error? If not, do you have any other alternative solution to my problem?

CodePudding user response:

Instead of rewriting your algorithm to accept more complicated iterators, make clients of your algorithm to provide the "simple" iterators to it. If clients have access to c 20 ranges, they can make a transformed range view. If they have access to boost, they can use boost::adaptors::transformed or boost::make_transform_iterator. For example:

std::map<int, SomeObject> mapWithValues {
    {obj1.getId(), obj1},
    {obj2.getId(), obj2},
    {obj3.getId(), obj3}
  };

auto get_second = [](const auto& val){ return val.second; };

auto objs_view = mapWithValues | boost::adaptors::transformed( get_second );

doSomethingWithMultipleObjectsInAnArbitraryContainer( objs_view.begin(),objs_view.end() );

CodePudding user response:

With C 20 or Boost <ranges> is the answer. Without, I'm stuck with templates. The trick is to have another template argument which replaces std::function as an argument. If I replace the doSomethingWithMultipleObjectsInAnArbitraryContainer() function with the one below, the example code compiles and works as expected.

template <typename Iter, typename Dereference>
void doSomethingWithMultipleObjectsInAnArbitraryContainer(Iter begin, Iter end, Dereference dereference) {
  if (begin == end) {
    // The container is empty. Nothing to do...
    return;
  }

  for (Iter it = begin; it != end;   it) {
    const SomeObject& obj = dereference(it);
    // Do something with the object...
    obj.doSomething();
  }
}

Credit to @smitsyn for helping here and to @Erlkoenig for providing the answer to this question.

  • Related