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?
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";
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With