Specialize template separately for data member and member functions

Viewed 123

I would like to specialize a template to do one thing on pointers to data members and another thing on pointers to member functions. This used to work up until gcc 11, with member functions acting as more specific. It still works with clang 11, but seems to have broken with gcc.

Here is a minimal non-working example:

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

As of gcc 11.1, compiling with g++ -std=c++17 memptr.cc gives:

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;
      |                                          ^~~~

Is this a bug in gcc, or a bug in my code? Either way, what is the simplest workaround?

3 Answers

I'm not sure, but I suspect this is a GCC bug. As a workaround, you can modify your code a bit by writing the signature of F in the argument-list of the specializations instead of the parameter-list, and deducing a type instead of a non-type

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

To use it, you need to write which<decltype(&S::i)>::desc since a type is needed.

Demo


If you want the actual pointer to be passed to the specializations instead of a type, you could also do the following, by letting the work be done by an existing type trait

// 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

Using @cigien's answer plus default template arguments, the following seems to work around what is probably a gcc bug in a backwards-compatible way:

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

In C++20 you could use concepts to make sure that the compiler does not match something like K = int() (like GCC does) by defining a concept such as

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

and then enforcing it for the template parameter 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!

Related