简体   繁体   中英

accessing class member regardless of it being a function or a data member

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

  1. Should this code compile? (It compiles only for GCC)
  2. 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() .

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 to obj.*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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM