I am implementing a smart pointer class template and I want to overload operator ->*
(even if it’s rarely done). I came across Scott Meyer’s article Implementing operator ->* for Smart Pointers . The article is from 1999 so I decided to try to adapt the code for C++ 14 (using parameter packs and perfect forwarding) and to improve it by adding support for pointer to data member—Meyer’s implementation only supports pointer to member functions—(which is the first of the two remaining problems of Meyer’s implementation that are listed at the section "Loose Ends" of his article and that he gives the reader as exercises to solve).
I ended up with the following code that seems to work quite fine. Now I have two questions:
- Is there anything that could be improved in my code?
- How to solve the second remaining problem listed at the section "Loose Ends" of Meyer’s article? ("You can’t use user-defined pointers-to-members. If someone has overloaded operator ->* to take objects that act like member pointers, you may want to support such ‘smart pointers to members’ in your smart pointer class. Unfortunately, you need traits classes to get the result type of such overloaded operator ->*.")
Here is the code:
#include<iostream>
template<typename T>
struct Member_function_type_traits { };
template<typename O, typename... A, typename R>
struct Member_function_type_traits<R (O::*)(A...)> {
typedef O Object_T;
typedef R Return_T;
};
template<typename O, typename... A, typename R>
struct Member_function_type_traits<R (O::*)(A...) const> {
typedef O Object_T;
typedef R Return_T;
};
template<typename O, typename... A, typename R>
struct Member_function_type_traits<R (O::*)(A...) volatile> {
typedef O Object_T;
typedef R Return_T;
};
template<typename O, typename... A, typename R>
struct Member_function_type_traits<R (O::*)(A...) const volatile> {
typedef O Object_T;
typedef R Return_T;
};
template<typename T>
class Pending_member_function_call {
typedef typename Member_function_type_traits<T>::Object_T Object_T;
typedef typename Member_function_type_traits<T>::Return_T Return_T;
std::pair<Object_T*, T> operands;
public:
Pending_member_function_call(std::pair<Object_T*, T> opr): operands{opr} { }
template<typename... U>
Return_T operator ()(U&&... args) {
return (operands.first->*operands.second)(std::forward<U>(args)...);
}
};
template<typename T>
class Smart_pointer {
T* ptr;
public:
Smart_pointer(T* ptr): ptr{ptr} { }
~Smart_pointer() { delete ptr; }
// ...
// operator ->* overload for pointers to data member
template<typename U, typename V>
V operator ->*(V U::* pdm) const {
return ptr->*pdm;
}
// operator ->* overload for pointers to member function
template<typename U>
Pending_member_function_call<U> operator ->*(U pmf) const {
return std::make_pair(ptr, pmf);
}
};
struct A {
int dm{5};
int mf(int x) { return 2 * x; }
};
int main() {
Smart_pointer<A> p{new A};
auto pdm = &A::dm;
auto pmf = &A::mf;
std::cout << "A::dm called: " << p->*pdm << '\n';
std::cout << "A::mf called: " << (p->*pmf)(8) << '\n';
return 0;
}
1 Answer 1
Is there anything that could be improved in my code?
I think you can accomplish the same thing in much less code with just a lambda:
template <class U>
auto operator->*(U pmf) const {
return [=](auto&&... args){
return (ptr->*pmf)(std::forward<decltype(args)>(args)...);
};
}
Could have a static_assert
on std::is_member_pointer
for extra safety.
How to solve the second remaining problem listed at the section "Loose Ends" of Meyer’s article?
I believe the lambda solves this problem as well.
Also you could consider const
propagation. If the Smart_pointer
is const
, do you really want to allow calling non-const
member functions? I don't know. If you decide you don't, you should provde both a const
and non-const
overload and forward to a helper:
template <class U>
auto operator->*(U pmf) {
return pending_mem_fun(ptr, pmf);
}
template <class U>
auto operator->*(U pmf) const {
return pending_mem_fun(const_cast<T const*>(ptr), pmf);
}
where pending_mem_fun
doesn't actually have to be a member function.
-
\$\begingroup\$ Lambdas of course! Very elegant solution, thanks. No, I don't want to allow calling non-
const
member functions when the type of the resource held by the smart pointer isconst
(same behavior as plain pointers). However your lambda already provides that, so there is no need to use two overloads and a helper instead: I think they implement the opposite behavior (they allow calling non-const
member functions) and you meant "If you decide you do". \$\endgroup\$Géry Ogam– Géry Ogam2016年01月13日 22:49:37 +00:00Commented Jan 13, 2016 at 22:49 -
\$\begingroup\$ @Maggyero No, I meant what I said. With the lambda solution, a
const Smart_pointer<T>
can call non-const
pointer-to-member functions. You'd have to use the alternate solution to disallow that. The reason is the difference between the typeT* const
(const pointer-to-non-const T) andT const*
(non-const pointer to const T) \$\endgroup\$Barry– Barry2016年01月14日 15:03:04 +00:00Commented Jan 14, 2016 at 15:03 -
\$\begingroup\$ I thought you were talking about
Smart_pointer<const T>
, notconst Smart_pointer<T>
. But then why would someone want to preventconst Smart_pointer<T>
from calling non-const
member functions? PlainT* const
do not prevent it: aconst
pointer is not a pointer-to-const
. And I don't think it is good design to prevent it for smart pointers. OnlySmart_pointer<const T>
should prevent it, and they already do naturally with your lambda implementation and mine. \$\endgroup\$Géry Ogam– Géry Ogam2016年01月15日 14:45:32 +00:00Commented Jan 15, 2016 at 14:45
Smart_pointer
otherwise you are going to end up wiht a double delete. \$\endgroup\$unique_ptr
from the GNU C++ Library and chapter VII of Modern C++ Design on smart pointers by Andrei Alexandrescu. What sources did you use for your articles? \$\endgroup\$