Предоставление общего интерфейса с TMP и SFINAE - PullRequest
0 голосов
/ 27 октября 2018

На данный момент у меня есть рабочий код ниже, где с использованием класса X я предоставляю общий интерфейс для нескольких классов - я ожидаю, что присутствует только статическая функция f, но я также не могу исправить тип возвращаемого значенияни параметры:

#include <iostream>
#include <type_traits>

class A {
public:
  static void f()
  {
    std::cout << "A::f()" << std::endl;
  }
};

class B {
public:
  static size_t f(std::string const& s_)
  {
    std::cout << "B::f()" << std::endl;
    return s_.size();
  }
};

class C {
};


template <typename T>
class X {
public:
  static
  void
  f(...)
  {
    std::cout << "Operation not supported!" << std::endl;
  }

  template <typename... Args>
  static
  std::result_of_t<decltype(&T::f)(Args...)>
  f(Args&&... args_)
  {
    return T::f(std::forward<Args>(args_)...);
  }
};

int main()
{
  X<A>::f();  // Compiles, but unexpected overload!
  auto const x = X<B>::f("Hello");  // Works just fine
  std::cout << x << std::endl;
//  X<C>::f();  // Does NOT compile!
  return 0;
}

Однако у меня есть несколько проблем с ним (как отмечено выше в комментариях):

  1. Если я разрешу функциине присутствовать и раскомментировать строку, используя C в качестве параметра шаблона, код не компилируется, он все еще ожидает, что C будет иметь функцию f:

    In instantiation of ‘class X<C>’:
    required from here
    error: ‘f’ is not a member of ‘C’
    std::result_of_t<decltype(&T::f)(Args...)>
    

    На основе это , я ожидал, что параметр многоточия добьется цели.

  2. С другой стороны, даже если бы это работало, у меня была бы другая проблема: хотя A обеспечивает fперегрузка с параметром многоточия выбирается во время разрешения перегрузки - где, конечно, f, предоставляемое A, является предпочтительным.

(Используемый компилятор: g ++ (Ubuntu 8.1.0-5ubuntu1 ~ 16.04) 8.1.0; используемый стандарт: C++ 14.)

Приветствуется любая помощь в решении вышеуказанных проблем (желательно обе одновременно).

1 Ответ

0 голосов
/ 27 октября 2018

Я предлагаю следующее X struct

template <typename T>
struct X
 {
  static void g (...)
   { std::cout << "Operation not supported!" << std::endl; }

  template <typename ... As, typename U = T>
  static auto g(int, As && ... as)
     -> decltype( U::f(std::forward<As>(as)...) )
   { return T::f(std::forward<As>(as)...); }

  template <typename ... As>
  static auto f (As && ... as)
   { return g(0, std::forward<As>(as)...); }
 };

Точки:

(1) Проход через функцию g() с дополнительным неиспользуемым параметром (int вВариантная версия, адсорбированная ... в «не поддерживаемой версии»), позволяет выбрать вариационную версию (называемую 0, то есть int), когда доступны функции sizeof...(As) == 0 и обе g().Это потому, что int является лучшим соответствием (в точном соответствии) для int как ....Но когда версия с переменным числом недоступна (см. Пункт (2)), «не поддерживаемая» версия все еще доступна (доступна только одна) и выбрана

(2) Необходимо включить / отключить SFINAE.Вариативная версия в соответствии с тем, что статический метод f() доступен (или не доступен) в T.К сожалению, T является аргументом шаблона класса, а не статического метода g(), поэтому вы можете использовать его для SFINAE.Хитрость заключается в том, что SFINAE работает с дополнительным параметром шаблона, U, который по умолчанию равен T

// ------------------------VVVVVVVVVVVVVV  additional U (defaulted to T) template type
template <typename ... As, typename U = T>
static auto g(int, As && ... as)
   -> decltype( U::f(std::forward<As>(as)...) )
// -------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// SFINAE enable this g() if (and only if) the call
//   U::f(std::forward<As>(as)...) is valid    

Ниже приведен упрощенный рабочий пример

#include <iostream>
#include <type_traits>

struct A
 { static void f() { std::cout << "A::f()" << std::endl; } };

struct B
 {
  static size_t f(std::string const& s_)
   { std::cout << "B::f()" << std::endl; return s_.size(); }
 };

struct C
 { };


template <typename T>
struct X
 {
  static void g (...)
   { std::cout << "Operation not supported!" << std::endl; }

  template <typename ... As, typename U = T>
  static auto g(int, As && ... as)
     -> decltype( U::f(std::forward<As>(as)...) )
   { return T::f(std::forward<As>(as)...); }

  template <typename ... As>
  static auto f (As && ... as)
   { return g(0, std::forward<As>(as)...); }
 };

int main ()
 {
   X<A>::f();  // now call variadic version
   auto const x = X<B>::f("Hello");  // Works just fine as before
   std::cout << x << std::endl;
   X<C>::f();  // now compile
 }
...