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?
4
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;
}
Live Demo
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.
As pointed out in the comments and the accepted answer, the problem was 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.
For someone with a similar problem that wants to see other ways to solve the problem, this is the solution I finally used for myself:
#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”.