I want to write a generic accessor for a class member regardless whether it is a function or or a data member:
#include <type_traits>
namespace traits
{
template <typename T, typename = void>
struct _name;
template <typename T>
struct _name<T, std::void_t<decltype(std::declval<T>().name)>>
{
constexpr decltype(auto) operator()(const T &t) const
{
return t.name;
}
};
template <typename T>
struct _name<T, std::void_t<decltype(std::declval<T>().name())>>
{
constexpr decltype(auto) operator()(const T &t) const
{
return t.name();
}
};
template <typename T>
decltype(auto) name(const T &t)
{
return _name<T>{}(t);
}
}
#include <string>
struct beb
{
std::string name = "beb";
};
struct bob
{
std::string name() const
{
return "bob";
}
};
#include <iostream>
int main()
{
std::cout << traits::name(bob());
std::cout << traits::name(beb());
}
I am using SFINAE with void_t
specialization, but it works with only single specialization. Otherwise it gives an error saying error C2953: 'traits::_name<T,void>': class template has already been defined
.
MSVC latest: https://godbolt.org/z/h9WT58z8P - does not compile
GCC 12.2: https://godbolt.org/z/vc3K1M7x5 - compiles
Clang 15.0.0: https://godbolt.org/z/dqGEMfYWK - does not compile
- Should this code compile? (It compiles only for GCC)
- How to make it compilable?
_name
is a customization point within a traits
namespace. By default it accesses name
using name
or name()
. But one can provide a specialization to use, for example getName()
.
CodePudding user response:
You seem to be reinventing std::invoke
. This function embodies the definition of Callable concept, and that definition has two special cases:
- a pointer to data member is "callable" like a function taking the object as its single parameter:
std::invoke(&C::dataMember, obj)
is equivalent toobj.*dataMember
- a pointer to member function is "callable" like a function taking the object as its first parameter:
std::invoke(&C::memFun, obj, a, b, c)
is equivalent to(obj.*memFun)(a, b, c)
Putting this together, your name
can be implemented simply as
template <typename T>
decltype(auto) name(const T &t)
{
return std::invoke(&T::name, t);
}
It will do the right thing whether name
is a data member or a member function. Demo
If you want _name
as a customization point, just add an extra indirection: make name
call _name
, and the default implementation of _name
call std::invoke
.