I’m trying to create a function object where, given a pointer to a member, it calls std::hash of the pointed-to member (or the result of calling the function member), but there’s something wrong about the partial specializations and it doesn’t compile:
#include <iostream>
#include <utility>
template<auto MemberPtr>
struct hash_member_t;
template<class R, class C>
struct hash_member_t<R C::*Member>
{
constexpr std::size_t operator()(C const& a) const noexcept
{ return std::hash<R>{}(a.*Member); }
};
template<class R, class C>
struct hash_member_t<R (C::*Member)()>
{
constexpr std::size_t operator()(C const& a) const noexcept
{ return std::hash<R>{}(a.*Member()); }
};
template<auto Member>
inline constexpr hash_member_t hash_member = hash_member_t<Member>{};
struct A
{
int* a = nullptr;
};
int main(int, char**)
{
A a;
std::cout << hash_member<&A::a>(a) << '\n';
return 0;
}
Compilation error:
main.cpp:8:34: error: template argument 1 is invalid
8 | struct hash_member_t<R C::*Member>
| ^
main.cpp:15:38: error: template argument 1 is invalid
15 | struct hash_member_t<R (C::*Member)()>
| ^
main.cpp: In instantiation of 'constexpr const hash_member_t<...auto...> hash_member<&A::a>':
main.cpp:32:18: required from here
main.cpp:22:32: error: invalid use of incomplete type 'struct hash_member_t<&A::a>'
22 | inline constexpr hash_member_t hash_member = hash_member_t<Member>{};
| ^~~~~~~~~~~
main.cpp:5:8: note: declaration of 'struct hash_member_t<&A::a>'
5 | struct hash_member_t;
| ^~~~~~~~~~~~~
The above is compiled with g++. With clang++:
main.cpp:8:28: error: type-id cannot have a name
struct hash_member_t<R C::*Member>
^~~~~~
main.cpp:8:22: error: template argument for non-type template parameter must be an expression
struct hash_member_t<R C::*Member>
^~~
main.cpp:4:15: note: template parameter is declared here
template<auto MemberPtr>
^
main.cpp:15:28: error: expected unqualified-id
struct hash_member_t<R (C::*Member)()>
^
main.cpp:15:29: error: use of undeclared identifier 'Member'
struct hash_member_t<R (C::*Member)()>
^
main.cpp:22:46: error: implicit instantiation of undefined template 'hash_member_t<&A::a>'
inline constexpr hash_member_t hash_member = hash_member_t<Member>{};
^
main.cpp:32:18: note: in instantiation of variable template specialization 'hash_member' requested here
std::cout << hash_member<&A::a>(a) << '\n';
^
main.cpp:5:8: note: template is declared here
struct hash_member_t;
^
5 errors generated.
What’s wrong with the syntax or the usage context?
My solution
As pointed out in the comments and the accepted answer, the problem is that the template parameter is a value, and so the partial specialization requires an actual value, like an actual pointer-to-member, but I intented to detect the type of the received pointer.
I finally made it by overloading instead of using auxiliary classes:
#include <iostream>
#include <utility>
template<auto MemberPtr>
struct hash_member_t
{
template<class T>
constexpr std::size_t operator()(T const& a) const noexcept
{ return _call(a, MemberPtr); }
private:
template<class R, class C>
constexpr std::size_t _call(C const& a, R C::*) const noexcept
{ return std::hash<R>{}(a.*MemberPtr); }
template<class R, class C>
constexpr std::size_t _call(C const& a, R (C::*)() const) const noexcept
{ return std::hash<R>{}((a.*MemberPtr)()); }
template<class R, class C>
constexpr std::size_t _call(C const& a, R (C::*)() const noexcept) const noexcept
{ return std::hash<R>{}((a.*MemberPtr)()); }
};
template<auto Member>
inline constexpr hash_member_t hash_member = hash_member_t<Member>{};
struct A
{
int a = 8;
std::string f() const noexcept { return "hi"; }
};
int main(int, char**)
{
A a;
std::cout << hash_member<&A::a>(a) << ' ' << hash_member<&A::f>(a) << '\n';
return 0;
}
This prints "8 11290347552884584064".
>Solution :
You are confusing non-type and type template arguments.
This template
template<auto MemberPtr> struct hash_member_t;
Has a non-type argument. Later you try to specialize it for types. I suppose there is a nicer way to write it, this is just what I got after fixing your code:
#include <iostream>
#include <utility>
template <typename T,T member> struct hash_member_impl;
template<class R, class C,R C::*member>
struct hash_member_impl<R C::*,member>
{
constexpr std::size_t operator()(C const& a) const noexcept
{ return std::hash<R>{}(a.*member); }
};
template<class R, class C,R (C::*member)()>
struct hash_member_impl<R (C::*)(),member>
{
constexpr std::size_t operator()(C const& a) const noexcept
{ return std::hash<R>{}(a.*member()); }
};
template<auto MemberPtr>
using hash_member = hash_member_impl<decltype(MemberPtr),MemberPtr>;
struct A
{
int* a = nullptr;
};
int main(int, char**)
{
A a;
std::cout << hash_member<&A::a>{}(a) << '\n';
return 0;
}
The important line is this
template<auto MemberPtr>
using hash_member = hash_member_impl<decltype(MemberPtr),MemberPtr>;
Your argument is a value, but you want to specialize on its type and this is how you get both the type and value from the auto non-type argument.