std::ranges::set_intersection doesn't do what you want here
If some element is found m times in [first1, last1) and n times in [first2, last2), the first min(m, n) elements will be copied from the first range to result.
You need a different algorithm, one that will use the values from both ranges. Adapting the possible implementation of set_intersection
template< class I1, class I2, class O, class F, class Comp = std::ranges::less, class Proj1 = std::identity, class Proj2 = std::identity >
concept joinable =
std::input_iterator<I1> &&
std::input_iterator<I2> &&
std::weakly_incrementable<O> &&
std::indirectly_writable<O, std::indirect_result_t<F&, I1, I2>> &&
std::indirect_strict_weak_order<Comp,
std::projected<I1, Proj1>,
std::projected<I2, Proj2>>;
struct join_fn {
template< std::input_iterator I1, std::sentinel_for<I1> S1,
std::input_iterator I2, std::sentinel_for<I2> S2,
std::weakly_incrementable O,
class Comp = std::ranges::less,
std::copy_constructible F,
class Proj1 = std::identity, class Proj2 = std::identity >
requires joinable<I1, I2, O, F, Comp, Proj1, Proj2>
constexpr std::ranges::binary_transform_result<I1, I2, O>
operator()( I1 first1, S1 last1, I2 first2, S2 last2, O result, F binary_op, Comp comp, Proj1 proj1 = {}, Proj2 proj2 = {} ) const {
while (!(first1 == last1 or first2 == last2)) {
if (std::invoke(comp, std::invoke(proj1, *first1), std::invoke(proj2, *first2)))
++first1;
else if (std::invoke(comp, std::invoke(proj2, *first2), std::invoke(proj1, *first1)))
++first2;
else
*result = std::invoke(binary_op, *first1, *first2), ++result, ++first1, ++first2;
}
return {std::ranges::next(std::move(first1), std::move(last1)),
std::ranges::next(std::move(first2), std::move(last2)),
std::move(result)};
}
template< std::ranges::input_range R1, std::ranges::input_range R2,
std::weakly_incrementable O,
class Comp = std::ranges::less,
std::copy_constructible F,
class Proj1 = std::identity, class Proj2 = std::identity >
requires joinable<std::ranges::iterator_t<R1>, std::ranges::iterator_t<R2>,
O, F, Comp, Proj1, Proj2>
constexpr std::ranges::binary_transform_result<std::ranges::borrowed_iterator_t<R1>,
std::ranges::borrowed_iterator_t<R2>, O>
operator()( R1&& r1, R2&& r2, O result, F binary_op, Comp comp = {},
Proj1 proj1 = {}, Proj2 proj2 = {} ) const {
return (*this)(std::ranges::begin(r1), std::ranges::end(r1),
std::ranges::begin(r2), std::ranges::end(r2),
std::move(result), std::move(binary_op), std::move(comp),
std::move(proj1), std::move(proj2));
}
} join;
And then you'll need to put them into a container that can hold the joined type.
struct ItemCombined{ int id; int data1; std::string data2; };
ItemCombined combine(Item1, Item2); // to implement
Finally you can
std::vector<Item1> v1 = { {.id=1, .data=42 }, {.id=2, .data=69 } };
std::vector<Item2> v2 = { {.id=1, .data="banana"s }, {.id=3, .data="coconut"s } };
std::vector<ItemCombined> select;
join(v1,v2,std::back_inserter(select),{},combine,&Item1::id,&Item2::id);
See it on coliru