I have the following situation: I must pack several pointers and an identifier into a tuple like this:
typedef tuple<unsigned*, unsigned*, unsigned*, unsigned> tuple_with_pointers_t;
Here, I have three pointers and one id. In other situations, I may have more or fewer pointers, but the last will be the id. Note that I used unsigned*
as an example only. It could be more complex objects.
Now, I want to compare the values of two such tuples. I.e., I need to dereference all tuple elements but the last. We can archive this using the following (in C++17):
template <size_t I = 0, typename T, typename... Ts>
constexpr bool lesser(std::tuple<T, Ts...> a, std::tuple<T, Ts...> b)
{
if constexpr (I < sizeof...(Ts))
return (*std::get<I>(a) < *std::get<I>(b)) ||
((*std::get<I>(a) == *std::get<I>(b)) && lesser<I + 1>(a, b));
else
return std::get<I>(a) < std::get<I>(b);
}
Such construct works very fine when we compare two tuples directly. Now, I would like to use lesser()
as a functor on the std::sort()
. But, both g++ and clang++ complain that they cannot "couldn't infer template argument '_Compare'". In other words, we need to pass the correct template arguments to lesser.
I have tried some things here, but with no success: we have three template parameters, and I am not sure how I can use the _Elements
from the tuple here. What will be the best strategy?
Here is some toy code:
#include <algorithm>
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
// My weird tuple with pointers and one unsigned index.
typedef tuple<unsigned*, unsigned*, unsigned*, unsigned> tuple_with_pointers_t;
// This works fine for two tuples directly. Note that we cannot dereference
// the last tuple element, so we compare it directly.
template <size_t I = 0, typename T, typename... Ts>
constexpr bool lesser(std::tuple<T, Ts...> a, std::tuple<T, Ts...> b)
{
if constexpr (I < sizeof...(Ts))
return (*std::get<I>(a) < *std::get<I>(b)) ||
((*std::get<I>(a) == *std::get<I>(b)) && lesser<I + 1>(a, b));
else
return std::get<I>(a) < std::get<I>(b);
}
int main() {
// Three sets of values.
vector<unsigned> values1 {1, 2, 3};
vector<unsigned> values2 {10, 20, 30};
vector<unsigned> values3 {11, 22, 33};
// Here, we pack it all together with the index.
vector<tuple_with_pointers_t> all;
for(unsigned i = 0; i < values1.size(); ++i)
all.emplace_back(&values1[i], &values2[i], &values3[i], i);
// So, it works if we want to compare two elements of our vector.
cout << "\n- t0 < t1: " << std::boolalpha << lesser(all[0], all[1]);
cout << "\n- t2 < t1: " << std::boolalpha << lesser(all[2], all[1]);
// Now, I want to sort the tuples by their values. The compiler doesn't
// like it: it cannot deduce the template parameters.
sort(all.begin(), all.end(), lesser);
return 0;
}
I appreciate any help, either using C++17 or C++20. But I'm looking for the most compact and elegant way to do it. It could be using a lambda function directly on the sort()
call, too, if possible.
Thanks!
Update:
OK, I found a little hack that works:
sort(all.begin(), all.end(),
[](const auto &a, const auto &b) {
return lesser(a, b);
}
);
Basically, we wrap it into a lambda, and therefore the compiler can deduce the types. But, can we do better?
Thanks