分别为数据成员和成员函数特化模板

Specialize template separately for data member and member functions

我想专门化一个模板来对指向数据成员的指针做一件事,对指向成员函数的指针做另一件事。这在 gcc 11 之前一直有效,成员函数的作用更为具体。它仍然适用于 clang 11,但似乎与 gcc 不兼容。

这是一个最小的非工作示例:

#include <iostream>

template<auto F> struct which;

template<typename K, typename V, K V::*F>
struct which<F> {
  static constexpr char desc[] = "pointer to data member";
};

template<typename K, typename V, K (V::*F)()>
struct which<F> {
  static constexpr char desc[] = "pointer to member function";
};

struct S {
  int i;
  int f() { return 0; }
};

int
main()
{
  std::cout << "S::i: " << which<&S::i>::desc << std::endl;
  std::cout << "S::f: " << which<&S::f>::desc << std::endl;
}

从 gcc 11.1 开始,使用 g++ -std=c++17 memptr.cc 编译会得到:

memptr.cc: In function 'int main()':
memptr.cc:24:40: error: ambiguous template instantiation for 'struct which<&S::f>'
   24 |   std::cout << "S::f: " << which<&S::f>::desc << std::endl;
      |                                        ^~
memptr.cc:6:8: note: candidates are: 'template<class K, class V, K V::* F> struct which<F> [with K = int(); V = S; K V::* F = &S::f]'
    6 | struct which<F> {
      |        ^~~~~~~~
memptr.cc:11:8: note:                 'template<class K, class V, K (V::* F)()> struct which<F> [with K = int; V = S; K (V::* F)() = &S::f]'
   11 | struct which<F> {
      |        ^~~~~~~~
memptr.cc:24:42: error: incomplete type 'which<&S::f>' used in nested name specifier
   24 |   std::cout << "S::f: " << which<&S::f>::desc << std::endl;
      |                                          ^~~~

这是 gcc 中的错误,还是我的代码中的错误?无论哪种方式,最简单的解决方法是什么?

我不确定,但我怀疑这是一个 GCC 错误。作为一种变通方法,您可以通过在专业化的 参数列表 而不是 参数列表中写入 F 的签名来稍微修改您的代码,并推导一个类型而不是非类型

template<typename F> struct which;

template<typename K, typename V>
struct which<K V::*> {
  static constexpr char desc[] = "pointer to data member";
};

template<typename K, typename V>
struct which<K (V::*)()> {
  static constexpr char desc[] = "pointer to member function";
};

要使用它,你需要写 which<decltype(&S::i)>::desc 因为需要一个类型。

Demo


如果您希望将实际指针而不是类型传递给特化,您也可以执行以下操作,让现有类型特征完成工作

// implementation
template<auto, bool> struct which_impl;

template<auto F>
struct which_impl<F, true> {
  static constexpr char desc[] = "pointer to data member";
};

template<auto F>
struct which_impl<F, false> {
  static constexpr char desc[] = "pointer to member function";
};

// helper alias
template<auto F> 
using which = which_impl<F, std::is_member_object_pointer_v<decltype(F)>>;

Demo

在 C++20 中,您可以使用 concepts 通过定义这样的概念来确保编译器不匹配 K = int() 之类的东西(就像 GCC 那样)作为

template <class T>
concept no_function = !std::is_function_v<T>;

然后为模板参数强制执行它 K

template<no_function K, typename V, K V::*F>
struct which<F> {
  static constexpr char desc[] = "pointer to data member";
};

template<no_function K, typename V, K (V::*F)()>
struct which<F> {
  static constexpr char desc[] = "pointer to member function";
};

Try it here!

使用@cigien 的答案加上默认模板参数,以下似乎以向后兼容的方式解决了可能是 gcc 错误的问题:

template<auto F,
     bool = std::is_member_function_pointer_v<decltype(F)>> struct which;

template<typename K, typename V, K V::*F>
struct which<F, false> {
  static constexpr char desc[] = "pointer to data member";
};

template<typename K, typename V, K (V::*F)()>
struct which<F, true> {
  static constexpr char desc[] = "pointer to member function";
};