About shared_ptr and pointer to member operator `->*` and `std::bind`
Asked Answered
T

3

11

Recently I discovered that shared_ptr does not have pointer to member operator ->*. I created simple example:

template <typename Pointer, typename Function, typename... Args>
auto invoke1(Pointer p, Function f, Args... args) -> decltype((p->*f)(args...))
{
  return (p->*f)(args...);
}
struct A { 
    void g() { std::cout << "A::g()\n"; } 
};
int main() {
  A a;
  invoke1(&a, &A::g); // works!!
  std::shared_ptr<A> sa = std::make_shared<A>();
  invoke1(sa, &A::g); // compile error!!
}

Q1: Why is so? Why shared_ptr does not have this operator?

I added such operator for shared_ptr and the example started to work:

template <typename T, typename Result>
auto operator ->* (std::shared_ptr<T> pointer, Result (T::*function)()) ->decltype(std::bind(function, pointer))
{
    return std::bind(function, pointer);
}
template <typename T, typename Result, typename Arg1>
auto operator ->* (std::shared_ptr<T> pointer, Result (T::*function)(Arg1 arg1)) ->decltype(std::bind(function, pointer, std::placeholders::_1))
{
    return std::bind(function, pointer, std::placeholders::_1);
}

Q2: Is this right implementation for this operator? Are there somewhere any "gold" rules how to implement such operator, probably either I reinvented the wheel or go in completely wrong direction, what do you think? Is there a way to have a single function implementing this operator instead of as many function as there are placeholders in std...

After that I came to conclusion that std::bind can be used in my invoke method.

template <typename Pointer, typename Function, typename... Args>
auto invoke2(Pointer p, Function f, Args... args) 
                     -> decltype(std::bind(f, p, args...)())
{
   return std::bind(f, p, args...)();
}

In this way my example also works without need to add operator ->* to shared_ptr.

Q3: So, is std::bind now considered as a replacement for operator->*?

Themselves answered 17/7, 2013 at 9:55 Comment(4)
did you tried: invoke1(sa.get(), &A::g);Delft
@Delft - yes, I've tried and of course it works, but I considered this workaround as not important for my question.Themselves
I didn't even know you can overload that operator.Rheinlander
@Mehrdad There was talk to allow overloading whitespace, and I've seen overloading of the comma operator as well. Sometimes even usefully!Despumate
D
6

In a nut shell: yes std::bind is a replacement for member function pointers.

Why? because member function pointers are awful, and their only purposes is to implement delegates, which is why std::bind and std::function do

For reference on how member function pointers are implemented, see my previous answer here. In simplest terms, member function pointers are crippled by the standard because they do not allow for calls after casts; this makes them quite pointless for the sort of behavior 90% of people want from member function pointers: delegates.

For this reason, std::function is used to represent an abstract "callable" type, with std::bind being used to bind a this to the member function pointer. You should absolutely not mess with member function pointers, and instead use std::bind and std::function.

Despumate answered 1/2, 2014 at 2:15 Comment(2)
Can you somehow explain "do not allow for calls after casts", some example? I read through your linked answer - however I do miss the explanation. Anyway, +1x2 for both your answers.Themselves
@Themselves One of the key features needed for using member function pointers as delegates is the ability to cast them between classes, for example, from a derived class to a base. While casting member function pointers is legal, calling one that was previously cast is not. It is undefined behavior. This means, for example, you cannot store a list of member function pointers and call them polymorphically. While this will work in almost all compilers, it is non-standard and therefore not C++.Despumate
M
4

I believe the simplest soultion would be to replace 'structure dereference' (->) operator with a pair of derefence(*) and structure reference(.) operators:

template <typename Pointer, typename Function, typename... Args>
auto invoke1(Pointer p, Function f, Args... args) -> decltype(((*p).*f)(args...))
{
  return ((*p).*f)(args...);
}
Mingo answered 31/1, 2014 at 18:34 Comment(4)
This is how INVOKE is defined, which mandates how std::bind worksNombril
.* is one operator, not two. Furthermore, ->* is not the same as ->.Trenatrenail
you can also do this: return p.get()->f(args...);Oppidan
@mchiasson, it will not allow pass raw pointer or some custom type of pointer that has no get function. But I believe every pointer supports dereference operator.Mingo
F
2

I believe shared_ptr does not have operator ->* because it's impossible to implement it for arbitrary number of arguments (which C++11 allows to do for other use cases). Also, you can easily add an overload of invoke function for smart pointers that calls get(), so complicating the interface is not desirable.

Fader answered 18/7, 2013 at 16:16 Comment(1)
It is impossible for arbitrary number of arguments, but is possible for let say up to 30 arguments (there are 29 placeholders in my gcc compiler). So practically speaking functions with more arguments are not present in real world. The second thing is that I know there are workarounds, @Delft already mentioned this. +1 for the observation that is cannot be done for any number of arguments. But I still want to know, maybe there are other reasons, and still want answers to other sub-questions.Themselves

© 2022 - 2024 — McMap. All rights reserved.