Reputation: 433
For sorting user defined typed objects in a flexible way (i.e. by naming a member variable) I wrote a template to generate lambdas to do the comparison. Additionally to chain comparisons of different member variables in case of equality I wrote a second template. It works so far but I want bpth templates to be completely independent from any concrete types. Therefore I have to get a class type from a class member pointer type.
This is my user defined example type:
struct Person { string name; int age, height; };
To sort objects of it by looking at e.g. the age I want to write it like:
auto result = max_element(persons.begin(), persons.end(), order_by(&Person::age));
This works with the template:
template<class F> //F is Person::* e.g. &Person::age
auto order_by(F f) {
return [f](const Person& smaller, const Person& bigger) {
return smaller.*f < bigger.*f;
};
}
To be able to chain multiple comparisons in case of equal values like this:
result = max_element(persons.begin(), persons.end(), order_by(&Person::age) | order_by(&Person::height));
I wrote the template:
//compose two orderings :
template<class F1, class F2>
auto operator|(F1 f1, F2 f2) {
return [f1, f2](auto a, auto b) {
auto res = f1(a, b);
auto inv_res = f1(b, a);
if (res != inv_res)
return res;
return f2(a, b);
};
}
Here the first comparison is done and if it detects that a==b (a is not smaller than b and b is not smaller than a) it uses the second comparison function.
What I want to achieve is to be independent of the Person type in the first template. How could this be solved?
Upvotes: 1
Views: 841
Reputation: 122133
You can get the class type from the type of a pointer to member like this:
#include <type_traits>
#include <iostream>
struct Foo {
int bar;
};
template <typename T>
struct type_from_member;
template <typename M,typename T>
struct type_from_member< M T::* > {
using type = T;
};
int main()
{
std::cout << std::is_same< type_from_member<decltype(&Foo::bar)>::type, Foo>::value;
}
Output:
1
Because type_from_member< decltype(&Foo::bar)>::type
is Foo
.
So you could use it like this:
template<class F> //F is Person::* e.g. &Person::age
auto order_by(F f) {
using T = typename type_from_member<F>::type;
return [f](const T& smaller, const T& bigger) {
return smaller.*f < bigger.*f;
};
}
Upvotes: 4
Reputation: 217065
I would forward job to std::tuple
with something like:
template <typename... Projs>
auto order_by(Projs... projs) {
return [=](const auto& lhs, const auto& rhs) {
return std::forward_as_tuple(std::invoke(projs, lhs)...)
< std::forward_as_tuple(std::invoke(projs, rhs)...);
};
}
with usage
result = std::max_element(persons.begin(), persons.end(), order_by(&Person::age, &Person::height));
ranges algorithms (C++20 or range-v3) separate comparison from projection, so you might have (by changing order_by
to project_to
):
result = ranges::max_element(persons, std::less<>{}, project_to(&Person::age, &Person::height));
Upvotes: 1
Reputation: 12928
You can easily extract the class and type of the pointer-to-member in your first template with some small modifications.
template<class Class, class Type>
auto order_by(Type Class::* f) {
return [f](const Class& smaller, const Class& bigger) {
return smaller.*f < bigger.*f;
};
}
Upvotes: 2