4
\$\begingroup\$

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:

  1. Is there anything that could be improved in my code?
  2. 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;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Don't forget to delete the copy constructor and the assignment operator of Smart_pointer otherwise you are going to end up wiht a double delete. \$\endgroup\$ – Martin York Jan 13 '16 at 21:33
  • \$\begingroup\$ Why a double delete? You can implement smart pointers with copy semantics. \$\endgroup\$ – Maggyero Jan 13 '16 at 23:05
  • \$\begingroup\$ You can. But unless you either delete the copy constructor or implement the copy semantics you are going to get a double delete. The default compiler generated copy constructor is doing the wrong thing. Look-up "Rule of Three (five)". \$\endgroup\$ – Martin York Jan 13 '16 at 23:07
  • 1
    \$\begingroup\$ See: Unique Ptr Followed by Shared Ptr followed by Smart Pointer Constructors \$\endgroup\$ – Martin York Jan 13 '16 at 23:11
  • \$\begingroup\$ Okay. Actually I am using my own copy constructor and copy assignment operator but thanks for the links, I will look at them. At the moment I used the implementation of 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\$ – Maggyero Jan 13 '16 at 23:33
5
\$\begingroup\$

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.

\$\endgroup\$
  • \$\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 is const (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-constmember functions) and you meant “If you decide you do”. \$\endgroup\$ – Maggyero Jan 13 '16 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 type T* const (const pointer-to-non-const T) and T const*(non-const pointer to const T) \$\endgroup\$ – Barry Jan 14 '16 at 15:03
  • \$\begingroup\$ I thought you were talking about Smart_pointer<const T>, not const Smart_pointer<T>. But then why would someone want to prevent const Smart_pointer<T> from calling non-const member functions? Plain T* const do not prevent it: a const pointer is not a pointer-to-const. And I don't think it is good design to prevent it for smart pointers. Only Smart_pointer<const T> should prevent it, and they already do naturally with your lambda implementation and mine. \$\endgroup\$ – Maggyero Jan 15 '16 at 14:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.