Using the following containers:
std::vector<std::pair<std::string, int>> keyVals = {
{"A", 1}, {"B", 2}, {"C", 3}
};
std::vector<std::string> keys = {
"A", "B", "C"
};
I need to construct a sorted output container
std::set<std::tuple<std::string, int, int>>
using the std::set_intersection between keyVals and keys.
These types use a common field (in my case a string) this is also used for pre-sorting these vectors via the default std::less - however no need in the above example as they are already sorted.
I need help to create a sorted container C that is not just a simple copy of elements from A to the output over the intersection range.
Instead I need to be able to construct each output element C - in my case a std::tuple<std::string, int, int> (where the first entry in the tuple is the common link string field and the other 2 int are some global fields.
To illustrate the problem I created a coliru live demo where I commented out the broken code - I don't know how to invoke a custom constructor for each iteration - please help.
#include <vector>
#include <variant>
#include <memory>
#include <iostream>
#include <algorithm>
// Helper to get Lambda with multiple signatures in place
// template deduction guide is a C++17 feature!
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
// this line is necessary for c++17 - not required for c++20
template<class... Ts> overload(Ts...) -> overload<Ts...>;
template<typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& vec) {
for (auto& el : vec) {
os << el.first << ' ';
}
return os;
}
// if c++17 is not available, you have to write a functor/function
// with both signatures
int main()
{
std::vector<std::pair<std::string, int>> keyVals = {
{"A", 1}, {"B", 2}, {"C", 3}
};
std::vector<std::string> keys = {
"A", "B", "C"
};
// std::set_intersection results can only be of the first iterator type
std::vector<std::pair<std::string, int>> intersection;
// this works
std::set_intersection(
keyVals.begin(), keyVals.end(),
keys.begin(), keys.end(),
std::back_inserter(intersection),
overload {
[](const std::pair<std::string, int>& lhs, const std::string& rhs) {
return lhs.first < rhs;
},
[](const std::string& lhs, const std::pair<std::string, int>& rhs) {
return lhs < rhs.first;
}
}
);
std::cout << intersection << std::endl;
#if 0
// std::set_intersection results can only be of the first iterator type
std::vector<std::tuple<std::string, int, int>> broken_intersection;
// this does not work as no match for 'operator=' *__result = *__first1;
std::set_intersection(
keyVals.begin(), keyVals.end(),
keys.begin(), keys.end(),
std::back_inserter(broken_intersection), // help here - I need to be able to
overload {
[](const std::pair<std::string, int>& lhs, const std::string& rhs) {
return lhs.first < rhs;
},
[](const std::string& lhs, const std::pair<std::string, int>& rhs) {
return lhs < rhs.first;
}
}
);
std::cout << broken_intersection << std::endl;
#endif
}
The problem is, as already noted, the different types of the data.
All input and output containers habe a different type.
And with that, 2 main operations in
std::set_differenceneed to be expressed.Problem 1 can be easily solved with a Functor, for which we will create 2 function call operators with the 2 needed signatures. This will allow for comparison in both directions.
Starting with C++14 you may also use a generic Lambda:
But then you need some common fields in both vectors.
The same approach would be possible with a Functor having a templated call operator:
Also here you need a common field. And they must have the same name. Maybe this will not fit here.
Or, we could create a wrapper for a Common Field:
Here the common field must just be of the same type.
Many possibilities
For problem 2 we need a small wrapper. Either for the first input iterator or the output iterator. The wrapper needs to have iterator functionality and a dereferencing operator that returns a Tuple, so that we can assign this Tuple to the resulting vector of Tuples.
So we will create a very small custom iterator, with only the minimumm functions needed by
std::set_intersectionWithin the iterator we will use a simple pointer to a Pair as the real iterator. The constructor can get a pointer to the Pairs in the first vector with the
std::vectordata function. That is very convenient.The dereferencing operator does the trick. It will not return a Pair, but construct a Tuple and return that. So, this is the conversion from the Pair to a Tuple.
Putting this altogether may lead to one of many potential solutions:
Looks a little bit clumsy
On a 2nd thought, you could convert both input vectors to the needed output type using
std::transform. But this will cost additional space and time.Anyway, please see the next potential solution:
This looks a little bit cleaner. But disadvantage is much more memory consumption and longer operation time.
Last but not least, we could implement an own
set_intersetcionfunction, which is quite simple.But in the end you need to decide based on other requirements or constraints.