7

Why doesn't std::shared_ptr have operator->*?

An implementation would seem easy using variadic templates.

See this paper for more info.

Edit: this seems like a potential duplicate of: About shared_ptr and pointer to member operator `->*` and `std::bind`

Community
  • 1
  • 1
Taylor
  • 5,871
  • 2
  • 30
  • 64
  • `std::shared_pointer` isn't a representation for a member function pointer. That kinda stuff is provided via `std::function` and `std::bind`. – πάντα ῥεῖ Jan 18 '15 at 18:51
  • 3
    @πάνταῥεῖ `std::shared_pointer` may, however, hold a class pointer. Then it would be natural to be able to write `(sp->*ptr_to_member)()` as a shorthand for `(sp.get()->*ptr_to_member)()`. The implementation for such a beast would be pretty hairy though. – Igor Tandetnik Jan 18 '15 at 18:55
  • 1
    @πάνταῥεῖ your comment makes no sense? – Yakk - Adam Nevraumont Jan 18 '15 at 18:55
  • sounds interesting, will read the paper – Michael Gazonda Jan 18 '15 at 18:56
  • "Unclear what I'm asking"? LOL – Taylor Jan 18 '15 at 18:57
  • 3
    @Taylor I'm guessing this question would go over better if we weren't required to click through a link – Michael Gazonda Jan 18 '15 at 18:58
  • 1
    That is an interesting question but not one, I think, that SO can be expected to answer with any degree of certainty. My guess is that no one thought of it or cared enough to write a proposal during standardization. – Wintermute Jan 18 '15 at 19:00
  • @MichaelGazonda, I'm not sure what else to add. – Taylor Jan 18 '15 at 19:00
  • @Wintermute, it's possible there's a pitfall with the implementation that I don't know of. – Taylor Jan 18 '15 at 19:02
  • @Taylor When I first read the question, I'm unsure of what "operator ->*" is or is supposed to do. Also, info on why you think it's important enough to be included in the standard might be helpful. Though I've also found that questions along the lines of "why aren't things different than how they are now" often aren't received well no matter how well they're put together. Maybe something along the lines of "hey here's this cool thing, how can I use it with std::shared_ptr" or something? – Michael Gazonda Jan 18 '15 at 19:04
  • In "related": http://stackoverflow.com/questions/17696664/about-shared-ptr-and-pointer-to-member-operator-and-stdbind?rq=1 – Mats Petersson Jan 18 '15 at 19:10
  • @MichaelGazonda, it's obvious what operator->* is supposed to do if you are familiar with it. It's also kind of obvious that something purporting to be a pointer should have as many operations of a pointer as possible, unless there are problems with doing so. Questions of the form "why aren't things different" are often very answerable. Answers include, "It won't work. Here's why." or "It suffers from this serious downside." etc. – Taylor Jan 18 '15 at 19:16
  • @Taylor I'm not disagreeing with you. Just trying to point out what would hopefully get your question to be better received. Also, I expect that `operator->*` isn't something many people are familiar with. – Michael Gazonda Jan 18 '15 at 19:19
  • @MichaelGazonda, they can look it up and find a better explanation that I have time to write. – Taylor Jan 18 '15 at 19:20
  • An issue is dealing with overloaded methods. `sp->*&foo::bar` cannot deal with an overloaded `foo::bar`? On phone so hard to check, but I suppose override resolution works with raw pointers and `->*`? – Yakk - Adam Nevraumont Jan 18 '15 at 19:28
  • @πάνταῥεῖ yes: the OP is pretty clearly talking about the shared_ptr on the lhs of `->*` not right. – Yakk - Adam Nevraumont Jan 18 '15 at 19:29
  • 2
    @Yakk That doesn't work with raw pointers either. –  Jan 18 '15 at 19:30
  • @hvd that is sad. :( – Yakk - Adam Nevraumont Jan 18 '15 at 19:53
  • @Yakk It's not usually a problem. If you can write `(p->*&S::f)()`, you can just write `p->f()`, you don't need a pointer-to-member there. `void (S::*pf)() = &S::f;` does work, forcing overload resolution to find a `void()` member function even if there are also different overloads. After which `(p->*pf)()` also works, because `p->*pf` is not an overloaded function. –  Jan 18 '15 at 20:01
  • @Wintermute Not only that, but standardization is an ongoing process. Either nobody has proposed it in 20+ years since iterators were invented, or it was proposed and rejected, perhaps as not being worth the effort. – Potatoswatter Jan 19 '15 at 01:37

2 Answers2

4

This could be added to std::shared_ptr after C++14 instead of the complex code you linked:

template<class Method>
auto operator->*(Method&& method){
  return [t=get(),m=std::forward<Method>(method)](auto&&args){
    return (t->*m)(std::forward<decltype(args)>(args)...);
  };
}

adding SFINAE optional. Note the above perfect forwards, which is imperfect. It also supports strange "method" types to a certain exten, so long as they produce somethung with an operator() and nothing else of importance.

This is still imperfect due to imperfections in perfect forwarding, so that might be a reason to leave it alone and force .get()->* instead. There are also some minor imperfections from the use of a lambda instead of a class, but those could be fixed.

Solutions that clone the interface also have flaws (they can move twice instead of once, or imply an exponential number of overloads).

Amusingly we can inject the above ->* without modifying std:

namespace notstd{
  template<class...Ts, class Method>
  auto operator->*(std::shared_ptr<Ts...> const& p, Method&& method){
    return [t=p.get(),m=std::forward<Method>(method)](auto&&args){
      return (t->*m)(std::forward<decltype(args)>(args)...);
    };
  }
  template<class...Ts, class Method>
  auto operator->*(std::unique_ptr<Ts...> const& p, Method&& method){
    return [t=p.get(),m=std::forward<Method>(method)](auto&&args){
      return (t->*m)(std::forward<decltype(args)>(args)...);
    };
  }
}

then using notstd::operator->* brings it into consideration. Amusingly, ->* need not be a non-static member of a class for it to be used unlike many of its relatives (like -> and []).

I included a similar one for unique_ptr, because why not.

An alternative option would store the shared_ptr within the lambda returned: it adds overhead in what looks like a low level operation, so I did not do it, and on unique_ptr it would be ill advised, if funny.

Now all the above is well and good, but doesn't answer the question.

A C++03 shared ptr (say, boost shared ptr) could have added:

template<class T, class R, class...Args>
struct mem_fun_invoke; // details, exposes `R operator()(Args...)const`
template<class T, class D, class R, class...Args>
mem_fun_invoke<T,R,Args...>
operator->*(std::shared_ptr<Ts...> const& p, R(T::*Method)(Args...)){
  return mem_fun_invoke<T,R,Args...>(p.get(), Method);
}

with ... emulated using macros (like in boost) or boilerplate code replication. This wouldn't be perfect (two copies of each arg made instead of one? I guess we could replace T args with T const& args to fix that), but it would be hard.

In comparison, in C++11 it is easy. But std shared ptr was designed along with C++11, and its precursors where designed before it. So to the precursors, adding ->* would be a lot of pain and boilerplate for little return, and C++11 shared ptr was written based off those.

This part, however, is just an opinion or a just-so-story.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

If you want it, you can add it yourself. operator ->* is an ordinary binary operator, and like operator +, it does not need to be a member.

Here is a slightly more general version, which will add ->* support to anything implementing operator ->.

template< typename obj, typename ptm >
auto operator ->* ( obj && o, ptm && p )
-> decltype( o.operator -> () ->* p )
    { return o.operator -> () ->* p; }

(A deduced return type isn't appropriate because it doesn't provide SFINAE, and then the template could interfere with other overloads. And, yes, the above implementation would be better if it forwarded rvalues. Finally, the drill-down behavior of operator -> cannot be captured without the use of a recursive template.)

As for why shared_ptr doesn't provide it natively, pointer-to-members are just often forgotten. No other part of the standard library has an operator ->*, although it would always have been appropriate for iterators and such. Neither is the functionality in the Boost.SmartPtr library which preceded the standard library feature. (Boost often has more bells and whistles.)

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421