Search code examples
c++sfinae

SFINAE type trait with pointer-to-member-function fails


I'm practicing SFINAE and would like to implement a type trait in order to check if a given class T contains a method print(). I have the following two variants:

// (1)

template <typename T, typename = int>
struct has_print_method : std::false_type {};

template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::true_type {};

template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
// (2)

template <typename T>
struct has_print_method{
    template <typename U, typename = void> struct helper : std::false_type{};
    template <typename U> struct helper<U, decltype(&U::print)> : std::true_type{};
    static const bool value = helper<T, void (T::*)() const>::value;
};

template <typename T>
const bool has_print_method_v = has_print_method<T>::value;

(1) only checks for the existence of a member method print() and ignores the member method's signature. Whereas (2) checks the method's signature, i.e. it requires a member method void print() const.

I test both variants via:

#include <iostream>
#include <type_traits>

// Simple Class A
struct A{
    int a;
    public:
      void print() const{}
};

// (1) or (2) here

template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
void print(T t) {
    t.print();
}

void print(double x){
    std::cout << x << '\n';
}


int main() {
    A a;
    print(a);
    print(1.0);  // (*)
    return 0;
}

Using the type trait (1) and compiling with clang 12.0 and std=c++17 flag works as expected. However, using (2) instead, I obtain

<source>:28:50: error: member pointer refers into non-class type 'double'
    static const bool value = helper<T, void (T::*)() const>::value;
                                                 ^
<source>:32:33: note: in instantiation of template class 'has_print_method<double>' requested here
const bool has_print_method_v = has_print_method<T>::value;
                                ^
<source>:34:39: note: in instantiation of variable template specialization 'has_print_method_v<double>' requested here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
                                      ^
<source>:35:6: note: while substituting prior template arguments into non-type template parameter [with T = double]
void print(T t) {
     ^~~~~~~~~~~~
<source>:47:5: note: while substituting deduced template arguments into function template 'print' [with T = double, $1 = (no value)]
    print(1.0);
    ^
1 error generated.

What am I missing here? You can find the example here on godbolt. Edit: Um, I have just noticed that both versions compile without an error with gcc11.1. Strange.


Solution

  • First up, judging by your error messages and my own tests, I think you mean that type trait (1) works and (2) doesn't. On that basis, here is a version of trait (1) that tests for a matching function signature:

    template <typename T, typename = int>
    struct has_print_method : std::false_type {};
    
    template <typename T>
    struct has_print_method<T, decltype(&T::print, 0)> : std::is_same <decltype(&T::print), void (T::*)() const> {};
    
    template <typename T>
    const bool has_print_method_v = has_print_method<T>::value;
    

    Live demo