10

I recently found out that the .* operator (and the closely related ->* operator) exists in C++. (See this question.)

Seems neat at first, but why would I ever need such a thing? The two answers in the linked question provided contrived examples which would benefit from a direct function call.

Where a direct function call is inconvenient, a function object could be used instead, like the lambda functions that may be used in std::sort. This removes a level of indirection and hence would be more performant than using .*.

The linked question also mentioned a simplified version of this example:

struct A {
    int a;
    int b;
};

void set_member(A& obj, int A::* ptr, int val){
    obj.*ptr = val;
}

int main()
{
    A obj;
    set_member(obj, &A::b, 5);
    set_member(obj, &A::a, 7);
    // Both members of obj are now assigned
}

But it's pretty trivial (perhaps even better practice because it's cleaner and isn't unnecessarily constrained to members of A) to do this instead:

struct A {
    int a;
    int b;
};

void set_me(int& out, int val){
    out = val;
}

int main()
{
    A obj;
    set_me(obj.b, 5);
    set_me(obj.a, 7);
    // Both members of obj are now assigned
}

In conclusion, a pointer-to-member-function might be replaced by a function object, and a pointer-to-member-variable might be replaced by a direct reference of said variable or a function object. Doing so might also increase the efficiency of the code due to one less indirection.

This question only provides examples where my conclusion stands, so it does not answer my question.

Apart from interfacing legacy code which uses .* (in which there would be no choice at all), when, really, would I want to use .*?

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • 1
    Possible duplicate of [C++: Pointer to class data member](https://stackoverflow.com/questions/670734/c-pointer-to-class-data-member) – Stargateur Jun 28 '17 at 03:46
  • maybe you are thinking trivial examples and need really do advanced examples but I suppose that this is not the place to do that – Emiliano Jun 28 '17 at 03:53
  • @Emiliano It would appear that all examples I've seen so far could be either replaced by a templated function object (which would provide a speed boost due to one less dereferencing) like a lambda function for `std::sort()` or simply a non-const reference. – Bernard Jun 28 '17 at 03:56
  • 2
    @Stargateur I've updated my question to explain why none of the answers in the other question are satisfactory. – Bernard Jun 28 '17 at 04:19
  • Is this a good enough example of pointer to members? https://stackoverflow.com/a/34165367/2104697 – Guillaume Racicot Jun 28 '17 at 04:33
  • 2
    @GuillaumeRacicot Nope. I wrote something similar before, but I simply added a third template parameter to `PropertyImpl` which is a Callable taking a single parameter of type `Class`, and returning a reference to the correct member. After all, you're already using templates. In that way, the type of the tuple element encapsulates sufficient information to find the correct member already, removing the need to store the pointer-to-member. – Bernard Jun 28 '17 at 04:44
  • @Bernard lambda are not constexpr before c++17, so you would need to write functions elsewhere, resulting in boilerplate. And a setter would be needed, so writing two functions for each property you'd like to have would be required, but my example is one small line by property. – Guillaume Racicot Jun 28 '17 at 04:48
  • @GuillaumeRacicot Sorry, I don't really understand what you mean. In my implementation, I'd do something like `property([] (Dog& x) -> decltype(x.barktype) { return x.barktype; });`, which wouldn't require `constexpr` because the parameter `x` isn't known at compile time. Or even `property([] (auto&& x) -> decltype(std::forward(x).barktype) { return std::forward(x).barktype; });`, but that's kinda long. – Bernard Jun 28 '17 at 05:00
  • @Bernard yes, of you have a more runtime based serialization it's applicable. In my example, properties are constexpr. Since properties are known at compile-time, you can serialize classes into a fixed structure like a tuple in a generic manner. Also, even if you don't need that, I find `&Dog::barktype` much less verbose than `[] (Dog& d) -> decltype(d.barktype) { return d.barktype }` – Guillaume Racicot Jun 28 '17 at 12:14
  • 1
    @Bernard the point is, sure you can write all the code without pointer to member. There will always be another way to implement things without pointer to member. But sometimes, pointer to members are making things much easier to write, and that justify their usage. – Guillaume Racicot Jun 28 '17 at 12:17

4 Answers4

6

You could create collections of pointers to members and iterate over them. E.g.:

struct UserStrings
{
    std::string first_name;
    std::string surname;
    std::string preferred_name;
    std::string address;
};

...

std::array<std::string UserStrings::*, 4> str_cols = { &UserStrings::first_name, &UserStrings::surname, &UserStrings::preferred_name, &UserStrings::address };
std::vector<UserStrings> users = GetUserStrings();

for (auto& user : users)
{
    for (auto& column : str_cols)
    {
        SanitizeForSQLQuery(user.*column);
    }
}
Tas
  • 7,023
  • 3
  • 36
  • 51
David Scarlett
  • 3,171
  • 2
  • 12
  • 28
  • This could be made faster by using templates, and doesn't need more code than your example. See my version [here](http://rextester.com/MOCOA27975). – Bernard Jun 28 '17 at 04:16
  • 2
    @Bernard: Yet in general case it induces code bloat. In real life the decision of whether to use run-time parameters (the above) or compile-time ones (templates) is not an easy one. So your argument about "made faster by using templates" is too primitive. Price of templates is code bloat, which can be massive in case of a larger function. The proper design of templated code involves knowing when is to switch to run-time parameterization in order to suppress unnecessary code bloat. Pointers-to-members is exactly the tool of run-time parameterization when it comes to member selection. – AnT stands with Russia Jun 28 '17 at 07:55
6

Your example is too trivial to be illustrative. Consider a bit more complicated one

struct A {
    int a;
    int b;
};

void set_n_members(A objs[], unsigned n, int A::* ptr, int val)
{
  for (unsigned i = 0; i < n; ++i)
     objs[i].*ptr = val;
}

int main()
{
    A objs[100];
    set_n_members(objs, 100, &A::b, 5);
    set_n_members(objs, 100, &A::a, 7);
}

How would you rewrite this without int A::* ptr and without inducing code bloat?

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
2

It is used to implement std::mem_fn, which is used to implement std::function.

The following code shows how the ->* operator works in a naive Function class implemention.

Similarly, you can implement a member invoker class using the .* operator and a class reference.

#include <iostream>

class A
{
public:
    void greet()
    {
        std::cout << "Hello world"<<std::endl;
    }
};

template<typename R, typename ...TArgs>
class Invoker 
{
public:
    virtual R apply(TArgs&& ...args) = 0;
};

template<typename C, typename R, typename ...TArgs>
class MemberInvoker :public Invoker<R, TArgs...>
{
protected:
    C*                          sender;
    R(C::*function)(TArgs ...args);

public:
    MemberInvoker(C* _sender, R(C::*_function)(TArgs ...args))
        :sender(_sender)
        , function(_function)
    {
    }

    virtual R apply(TArgs&& ...args) override
    {
        return (sender->*function)(std::forward<TArgs>(args)...);
    }
};

template<typename T>
class Func
{
};

template<typename R, typename ...TArgs>
class Func<R(TArgs...)>
{
public:
    Invoker<R,TArgs...>* invoker=nullptr;

    template<typename C>
    Func(C* sender, R(C::*function)(TArgs...))
    {
        invoker =new MemberInvoker<C, R, TArgs...>(sender, function);
    }

    R operator()(TArgs&& ...args)
    {
        return  invoker->apply(std::forward<TArgs>(args)...);
    }

    ~Func()
    {
        if (invoker)
        {
            delete invoker;
            invoker = nullptr;
        }
    }
};

int main()
{
    A a;
    Func<void()> greetFunc(&a, &A::greet);
    greetFunc();
    system("PAUSE");
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
MaxC
  • 63
  • 6
0

Let's say you wanted to write a LINQ style library for C++ that could be used something like this:

struct Person
{
    std::string first_name;
    std::string last_name;
    std::string occupation;
    int age;
    int children;
};

std::vector<Person> people = loadPeople();
std::vector<std::string> result = from(people)
     .where(&Person::last_name == "Smith")
     .where(&Person::age > 30)
     .select("%s %s",&Person::first_name,&Person::last_name);
for(std::string person : result) { ... };

Under the covers, the where function accepts an expression tree containing a pointer to member (among other stuff) and is applied to each vector item looking for one that matches. The select statement accepts a format string and some pointer to members and does an sprintf style formatting of whichever vector items get through the where statements.

I have written something like this, and there are several others out there that do it slightly differently (Is there a LINQ library for C++?). Pointer-to-member allows the library user to specify whichever members of their struct that they want and the library doesn't need to know anything about what they might do.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Jerry Jeremiah
  • 9,045
  • 2
  • 23
  • 32
  • How could `&Person::last_name == "Smith"` ever be made to compile? Also, wouldn't writing selectors as lambda functions like [this](http://cpplinq.codeplex.com/) or like `std::find_if` and `std::remove_if` work too (and as an added benefit, make the code faster)? On a side note, the Ranges TS seems to be taking a similar route. – Bernard Jun 28 '17 at 04:28
  • @Bernard the function accepts an expression tree. The operator== accepts a pointer to member and a value and returns a functor that when executed does what it does. But the expression tree creation is compile time so it is reasonably fast. There are maybe a dozen of these LINQ style libraries out there - all of them take their parameters slightly differently but the ones that work on arrays of structures use pointer to member to select the member they want. Like I said I wrote something like this myself several years ago... – Jerry Jeremiah Jun 28 '17 at 04:39
  • @Bernard I wrote the code from memory so it's possible the syntax isn't exactly right. Possibly using lambdas would be way better but I wrote my version of that LINQ library in 2005 using an ancient compiler so you could be right that there is no reason NOW to use these features - perhaps they were only useful before lambdas? – Jerry Jeremiah Jun 28 '17 at 04:46
  • At runtime there would be one indirection more to calculate the address of `x.last_name` when supplied with the `x`. Perhaps before C++11 lambdas the trouble of writing additional classes with defined `operator()` would outweigh the benefits of not needing to read the object offsets at runtime. – Bernard Jun 28 '17 at 04:51
  • 1
    @Bernard The main problem with a separate functor for every comparison you want is that they are non local and you can't see what is really happening in the code that fits on the screen. I can imagine having functions like last_name_is() and age_less_than() and others so you can call find_if() et al but the reason lambdas were put in the standard was because expression trees were so useful and everybody was using them for lambdas. So you are right - rolling your own isn't as good as using standard lambdas and pointer to member is probably not the best practice if you have a new-ish compiler. – Jerry Jeremiah Jun 28 '17 at 05:03