Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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`

like image 903
Taylor Avatar asked Jan 18 '15 18:01

Taylor


Video Answer


2 Answers

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.

like image 103
Yakk - Adam Nevraumont Avatar answered Nov 02 '22 17:11

Yakk - Adam Nevraumont


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.)

like image 21
Potatoswatter Avatar answered Nov 02 '22 15:11

Potatoswatter