1

I try to write a generic member Delegate function (based on this cool answer generic member function pointer as a template parameter):

template <typename T, T>
class Delegate {};

template <typename T, typename R, typename... Args, R (T::*TMember)(Args...)>
class Delegate<R (T::*)(Args...), TMember> {
 public:
  Delegate(T &obj) : obj_{obj} {}

  R operator()(Args &&... args) const noexcept(
      noexcept((obj_.*TMember)(std::forward<Args>(args)...))) {
    return (obj_.*TMember)(std::forward<Args>(args)...);
  }

 private:
  T &obj_;
};

template <typename T, typename R, typename... Args, R (T::*TMember)(Args...) const>
class Delegate<R (T::*)(Args...) const, TMember> {
 public:
  Delegate(const T &obj) : obj_{obj} {}

  R operator()(Args &&... args) const noexcept(
      noexcept((obj_.*TMember)(std::forward<Args>(args)...))) {
    return (obj_.*TMember)(std::forward<Args>(args)...);
  }

 private:
  const T &obj_;
};

struct Foo {
  int Bar(int a, int b) noexcept { return a + b; }

  int ConstBar(int a, int b) const noexcept { return a + b; }
};

int main() {
  Foo obj;
  auto add = Delegate<int (Foo::*)(int, int), &Foo::Bar>(obj);

  std::cout << add(1, 2) << std::endl;  // 3

  const Foo const_obj;
  auto const_add =
      Delegate<int (Foo::*)(int, int) const, &Foo::ConstBar>(const_obj);

  std::cout << const_add(3, 4) << std::endl; // 7

  return 0;
}

So, the questions are:

  1. Can i somehow omit fuzzy pointer to member in Delegate instantiation, like this: Delegate<Foo::Bar>(obj)?
  2. I'm lazy, and specifying member signature two times looks too hard for me. Can we somehow deduce template signature based on single template argument, so Delegate<int (Foo::*)(int, int), &Foo::Bar>(obj) becomes Delegate<&Foo::Bar>(obj)?
  3. To write generic code i need to propagate noexcept specifier from member to Delegate's operator() via noexcept(noexcept((obj_.*TMember)(std::forward<Args>(args)...))). May noexcept specifier be derived from return body itself, instead of specifying body both times?
  4. I need to specialize both versions - const and non-const ones. May compiler do this for me? What about volatile / const volatile specializations, should i copy the same code again and again?

If smth is impossible, please, describe why, or, at least, where can i read about :)

max66
  • 65,235
  • 10
  • 71
  • 111
dimhotepus
  • 64
  • 8

1 Answers1

1
  1. Can i somehow omit fuzzy pointer to member in Delegate instantiation, like this: Delegate(obj)?

According this answer... I suppose the answer is "no".

  1. I'm lazy, and specifying member signature two times looks too hard for me. Can we somehow deduce template signature based on single template argument, so Delegate(obj) becomes Delegate<&Foo::Bar>(obj)?

You tagged C++17, so... yes: you can use auto

template <auto>
class Delegate
 { };

template <typename T, typename R, typename... Args,
          R (T::*TMember)(Args...)>
class Delegate<TMember>

// ...

template <typename T, typename R, typename... Args,
          R (T::*TMember)(Args...) const>
class Delegate<TMember>

// ...


   // ...    

   auto add = Delegate<&Foo::Bar>(obj);

   // ...

   auto const_add =
      Delegate<&Foo::ConstBar>(const_obj);
  1. I need to specialize both versions - const and non-const ones. May compiler do this for me? What about volatile / const volatile specializations, should i copy the same code again and again?

Maybe adding a level of indirection?

I mean... if you create a base class for Delegate as follows

template <auto TMember, typename T, typename R, typename ... Args>
class DelegateBase
 {
   public:
      DelegateBase (T & obj) : obj_{obj}
       { }

      R operator() (Args &&... args) const
         noexcept
         (noexcept((std::declval<T>().*TMember)(std::forward<Args>(args)...)))
       { return (obj_.*TMember)(std::forward<Args>(args)...); }

   private:
      T & obj_;
 };

you can write Delegate using DelegateBase

template <auto>
class Delegate
 { };

template <typename T, typename R, typename... Args,
          R (T::*TMember)(Args...)>
class Delegate<TMember>
   : public DelegateBase<TMember, T, R, Args...>
 { using DelegateBase<TMember, T, R, Args...>::DelegateBase; };

template <typename T, typename R, typename... Args,
          R (T::*TMember)(Args...) const>
class Delegate<TMember>
   : public DelegateBase<TMember, T const, R, Args...>
 { using DelegateBase<TMember, T const, R, Args...>::DelegateBase; };

I suppose you can add a couple of volatile/const volatile specializations.


Off Topic: if you want to use perfect forwarding, you have to use forwarding references, not r-value references.

I mean... you can't use perfect forwarding in your operator()

  R operator()(Args &&... args) const noexcept(
      noexcept((obj_.*TMember)(std::forward<Args>(args)...))) {
    return (obj_.*TMember)(std::forward<Args>(args)...);
  } // ....................^^^^^^^^^^^^^^^^^^ wrong

because the Args... pack is a variadic parameter of the class, not of the operator; so in operator(), the Args && ... args are r-value references (so std::move).

If you want to use perfect forwarding, you have to use template parameters of the operator() itself, so

  template <typename ... As>
  R operator()(As &&... args) const noexcept(
      noexcept((obj_.*TMember)(std::forward<As>(args)...))) {
    return (obj_.*TMember)(std::forward<As>(args)...);
  } // ....................^^^^^^^^^^^^^^^^ ok
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thank you! Looks exactly as i want. Was screwed by Visual Studio 2019 intellisense in a CMake project. – dimhotepus Oct 02 '19 at 20:52
  • Good solution for specializations copy-past problem! May be you also know how to skip copy-paste body in `noexcept` and remove noisy pointer to member in `Delegate` instantiation? – dimhotepus Oct 02 '19 at 21:11
  • @dimhotepus - not at the moment; but I'm thinking about 1 and 3. – max66 Oct 02 '19 at 21:13
  • @dimhotepus - added an answer ("no") to point 1; I suppose the answer is "no" also for point 3 but I'm not sure. – max66 Oct 02 '19 at 21:34
  • 1
    @dimhotepus - added an off topic about perfect forwarding; hope this helps. – max66 Oct 02 '19 at 21:42
  • Thank you for all answers! Especially for perfect forwarding. Unfortunately, i'm stuck with SFINAE and partial specialization for `volatile` / `const volatile`. I've tried smth like this: `, std::enable_if_t, R> (T::*TMember)(Args...)> class Delegate : public DelegateBase { using DelegateBase::DelegateBase; };` but it didn't help. Also i can't define additional template parameter with `std::enable_if`, as partial template specialization disallows default parameter. – dimhotepus Oct 02 '19 at 22:04