Определить универсальную функцию-член в качестве параметра шаблона без избыточной подписи члена в качестве параметра - PullRequest
1 голос
/ 02 октября 2019

Я пытаюсь написать универсальную функцию-член Delegate (на основе этого интересного ответа указатель универсальной функции-члена в качестве параметра шаблона ):

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;
}

Итак, вопросы:

  1. Могу ли я как-то опустить нечеткий указатель на член в Delegate экземпляре, например: Delegate<Foo::Bar>(obj)?
  2. Я ленив, и указание подписи члена два раза выглядит слишкомтяжело для меня. Можем ли мы каким-то образом вывести сигнатуру шаблона на основе одного аргумента шаблона, поэтому Delegate<int (Foo::*)(int, int), &Foo::Bar>(obj) становится Delegate<&Foo::Bar>(obj)?
  3. Чтобы написать общий код, мне нужно распространить спецификатор noexcept от члена до Delegate * operator()через noexcept(noexcept((obj_.*TMember)(std::forward<Args>(args)...))). Может ли спецификатор noexcept быть получен из самого тела return, вместо того, чтобы указывать тело оба раза?
  4. Мне нужно специализировать обе версии - const и не-1027 *. Может ли компилятор сделать это для меня? Как насчет volatile / const volatile специализаций, должен ли я копировать один и тот же код снова и снова?

Если что-то невозможно, пожалуйста, опишите почему или, по крайней мере, где я могу прочитать о:)

1 Ответ

1 голос
/ 02 октября 2019
  1. Могу ли я как-то опустить нечеткий указатель на член в экземпляре делегата, например: Delegate (obj)?

Согласно этому ответу ... я полагаю, что ответ "нет".

Мне лень, и указание подписи члена два раза кажется мне слишком сложным. Можем ли мы каким-то образом вывести сигнатуру шаблона на основе одного аргумента шаблона, поэтому Delegate (obj) становится Delegate <& Foo :: Bar> (obj)?

Вы пометили C ++ 17, так. .. да: вы можете использовать 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);
Мне нужно специализировать обе версии - константную и неконстантную. Может ли компилятор сделать это для меня? Как насчет volatile / const volatile специализаций, должен ли я копировать один и тот же код снова и снова?

Может быть, добавить уровень косвенности?

Я имею в виду ... если высоздайте базовый класс для Delegate следующим образом

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_;
 };

, который вы можете написать Delegate, используя 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; };

Полагаю, вы можете добавить пару volatile /const volatile специализаций.


Не по теме: если вы хотите использовать идеальную пересылку, вы должны использовать ссылки на пересылку, а не ссылки на r-значения.

Я имею в виду ... выне может использовать идеальную пересылку в вашем operator()

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

, потому что пакет Args... - это переменный параметр класса, а не оператора;поэтому в operator() Args && ... args являются ссылками на r-значение (например, std::move).

Если вы хотите использовать идеальную пересылку, вы должны использовать параметры шаблона самого operator(), поэтому

  template <typename ... As>
  R operator()(As &&... args) const noexcept(
      noexcept((obj_.*TMember)(std::forward<As>(args)...))) {
    return (obj_.*TMember)(std::forward<As>(args)...);
  } // ....................^^^^^^^^^^^^^^^^ ok
...