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.