yohhoyの日記

技術的メモをしていきたい日記

std::shared_ptr型と->*演算子とstd::invoke関数

C++標準ライブラリのスマートポインタ型std::shared_ptr<T>では->*演算子オーバーロードを提供しない(注:->はあるよ)。

#include <memory>

struct X { int mf(); };
// メンバ関数ポインタ
int (X::*pmf)() = &X::mf;

// (通常)ポインタ型
X* p = new X;
p->mf();      // OK
(p->*pmf)();  // OK

// std::shared_ptr型
std::shared_ptr<X> sp{ p };
sp->mf();      // OK
(sp->*pmf)();  // NG!
(sp.get()->*pmf)();  // OK: get()でポインタを取出す
(&*sp->*pmf)();      // OK: 暗号じみた記法

C++17で追加されたstd::invoke関数を利用すると、メンバ関数ポインタ呼び出しであっても通常ポインタとスマートポインタ型を統一的に扱える。

#include <functional>

std::invoke(pmf, p);   // OK: X*
std::invoke(pmf, sp);  // OK: std::shared_ptr<X>

// 代替(C++03/11/14)
(*s.*pmf)();   // OK
(*sp.*pmf)();  // OK

マイナー機能のため有用性は低いが、技術的には->*演算子オーバーロードを提供可能。C++14機能までを使った実装例(エッセンスのみ):

// スマートポインタ型
template <typename T>
struct SP {
  T* p_ = nullptr;
  SP(T* p): p_(p) {}

  // ->演算子オーバーロード
  T* operator->() { return p_; }

  // ->*演算子オーバーロード
  template<typename R, typename... Ts>
  auto operator->*(R (T::*pmf)(Ts...)) {
    return [p=p_, pmf](Ts&&... args) -> R {
      return (p->*pmf)(std::forward<Ts>(args)...);
    };
  }
};

// スマートポインタ型
SP<X> sp{ p };
sp->mf();      // OK
(sp->*pmf)();  // OK

関連URL