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
CodePudding user response:
As suggested in the comments, you can add your comparator into a function object and pass an instance of the object to sort
:
#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;
namespace details {
template <size_t I = 0, typename T, typename... Ts>
constexpr bool lesser(std::tuple<T, Ts...> const& a, std::tuple<T, Ts...> const& 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);
}
}
struct Less
{
template <typename... Ts>
constexpr bool operator()(std::tuple<Ts...> const& a, std::tuple<Ts...> const& b)
{
return details::lesser<0, Ts...>(a, 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 << Less()(all[0], all[1]);
cout << "\n- t2 < t1: " << std::boolalpha << Less()(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(), Less());
return 0;
}
As an alternative, you could wrap your unsigned*
in a custom pointer type and provide a comparator for it. Then you can use the default comperator for tuples, which compares the elements lexicographically.
I personally would prefer this approach, because the code is much more readable. I don't know if this would break your existing code or would entail a huge refactor.
#include <algorithm>
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
class Ptr
{
public:
Ptr(unsigned& v) : m_ptr(&v) {}
unsigned operator*() const {
return *m_ptr;
}
private:
unsigned* m_ptr;
};
bool operator<(Ptr const& l, Ptr const& r)
{
return *l < *r;
}
// My weird tuple with pointers and one unsigned index.
typedef tuple<Ptr, Ptr, Ptr, unsigned> tuple_with_pointers_t;
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 << (all[0] < all[1]);
cout << "\n- t2 < t1: " << std::boolalpha << (all[2] < all[1]);
sort(all.begin(), all.end());
return 0;
}
CodePudding user response:
I think we can use this. Of course, I don't know your tuple can be more complex.
template<typename T, size_t I = 0>
using type_tuple = typename std::tuple_element<I,T>::type;
template<size_t I = 0, template<typename> class F = std::less_equal>
struct TupleCompare
{
template<typename T>
bool operator()(T const &t1, T const &t2){
using _type = typename std::conditional<std::is_pointer<type_tuple<T>>::value,
typename std::remove_pointer<type_tuple<T,I>>::type, type_tuple<T>>::type;
if constexpr (I == std::tuple_size_v<T> - 1) {
return F<_type>()(std::get<I>(t1), std::get<I>(t2));
} else {
return F<_type>()(*std::get<I>(t1), *std::get<I>(t2)) && TupleCompare<I 1, F>()(t1, t2);
}
}
};
CodePudding user response:
By doing a non-"recursive" function, you might do a "one-liner":
sort(all.begin(), all.end(),
[]<typename T>(const T& lhs, const T& rhs) {
return [&]<std::size_t... Is>(std::index_sequence<Is...>){
return std::tie(std::get<Is>(lhs)...)
< std::tie(std::get<Is>(rhs)...);
}(std::make_index_sequence<std::tuple_size_v<T> - 1>{});
});
template lambda are C 20.
Without that, at least an helper function is required, so it become, as the other solutions, wrapping a function in a functor.