Search code examples
c++sfinae

member variable detection based on sfinae not work


Given the following code. I want to detect any class that has an age member field based on sfinae.

template <typename>
struct Void {
  using type = void;
};

template <typename T, typename U = void>
struct HasAge : std::false_type {};

template <typename T>
struct HasAge<T, typename Void<decltype(declval<T>().age)>::type>
    : std::true_type {};

struct Empty {};

struct PrivatePerson {
 private:
  int age;
};

struct PublicPerson {
 public:
  PublicPerson() = default;
  int age;
};

template <typename T, typename = void>
struct HasPointer : std::false_type {};

template <typename T>
struct HasPointer<T, typename Void<typename T::pointer>::type>
    : std::true_type {};

struct FancyPointer {
  using pointer = int;
};

struct HazardPointer {
  template <typename T>
  struct pointer {};
};

int main() {
  using namespace std;

  cout << boolalpha;

  cout << HasAge<Empty>::value << endl;           // false
  cout << HasAge<PrivatePerson>::value << endl;   // false
  cout << HasAge<PublicPerson>::value << endl;    // false

  cout << HasPointer<FancyPointer>::value << endl; // true
  cout << HasPointer<HazardPointer>::value << endl;// false
}

The above code can't detect whether any class has an age member field or not. It seems only the primary template is applied. But this strategy seems to work for typedef or using an alias inside a class. It could detect class defines a pointer type or not. Could anyone tell me why age detection failed? Thx.


Solution

  • In the posted code, this template:

    template <typename T>
    struct HasAge<T, typename Void<decltype(declval<T>().age)>::type>
    //                                      ^^^^^^^   
        : std::true_type {};
    

    Is defined before a using namespace std; statement, which is in main.

    Compiling with g++ -std=c++11 -Wall -Wextra -pedantic, we get errors:

    <source>:14:41: error: 'declval' was not declared in this scope; did you mean 'std::declval'?
       14 | struct HasAge<T, typename Void<decltype(declval<T>().age)>::type>
          |                                         ^~~~~~~
          |                                         std::declval
    

    Clang, with the same arguments, emits a warning1 instead:

    <source>:14:41: warning: use of function template name with no prior declaration in
    function call with explicit template arguments is a C++20 extension [-Wc++20-extensions]
    struct HasAge<T, typename Void<decltype(declval<T>().age)>::type>
    

    If we compile with g++ using -std=c++20 -Wall -Wextra -fpermissive, we obtain a similar warning and the same (unexpected) result2 as OP's.

    <source>:13:41: warning: there are no arguments to 'declval' that depend on a template parameter, so a declaration of 'declval' must be available [-fpermissive]
       13 | struct HasAge<T, typename Void<decltype(declval<T>().age)>::type>
          |                                         ^~~~~~~~~~
    

    To make this pre-C++20 detection idiom implementation work3, we just need to fully qualify std::declval.


    1) https://godbolt.org/z/dPc5aMEr8
    2) https://godbolt.org/z/rP74hnKhs
    3) https://godbolt.org/z/94fqcTEs7