提供与 TMP 和 SFINAE 的通用接口

Providing generic interface with TMP and SFINAE

目前,我有以下使用 class X 的工作代码,我为多个 classes 提供了一个通用接口 - 我只期望静态 f 函数存在,但我既不修复 return 类型也不修复参数:

#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...)>
    

    基于 this,我希望省略号参数可以解决问题。

  2. 另一方面,即使这行得通,我还会遇到另一个问题:虽然 A 提供了 f,但重载 省略号参数是在重载解析期间选择的 - 当然,其中 A 提供的 f 是首选。

(使用的编译器:g++ (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0;使用的标准:C++14。)

欢迎任何帮助解决上述问题(最好同时解决两个问题)。

我提出以下X结构

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,被"not supported version"中的...吸附),允许select 可变参数版本(使用 0 调用,即 int),当 sizeof...(As) == 0 和两个 g() 函数可用时。这是因为 int 对于 int... 更匹配(完全匹配)。但是当可变版本不可用时(见第(2)点),"non supported" 版本仍然可用(唯一可用的)和 selected

(2) 根据 f() 静态方法在 T 中可用(或不可用)这一事实,您必须使用 SFINAE enable/disable 可变参数版本。不幸的是 T 是 class 的模板参数,而不是 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
 }