Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specialize template separately for data member and member functions

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?

like image 368
user3188445 Avatar asked Jan 24 '23 08:01

user3188445


2 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

like image 172
cigien Avatar answered Apr 27 '23 20:04

cigien


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";
};
like image 36
user3188445 Avatar answered Apr 27 '23 22:04

user3188445