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.