Daniel
Daniel

Reputation: 433

How to get class type from member pointer template type

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

Answers (3)

463035818_is_not_an_ai
463035818_is_not_an_ai

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

Jarod42
Jarod42

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

super
super

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

Related Questions