Как проверить, существует ли имя члена (переменная или функция) в классе, с указанием типа или без него?

Этот Q является расширением:
Можно ли написать шаблон C++ для проверки существования функции?

Есть ли какая-нибудь утилита, которая поможет найти:

  • Если имя члена существует внутри класса или нет? Этот член может быть переменной или методом.
  • Указание типа члена должно быть необязательным

3 ответа

Решение

C++03

#define HasMember(NAME) \
  template<class Class, typename Type = void> \
  struct HasMember_##NAME \
  { \
    typedef char (&yes)[2]; \
    template<unsigned long> struct exists; \
    template<typename V> static yes Check (exists<sizeof(static_cast<Type>(&V::NAME))>*); \
    template<typename> static char Check (...); \
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
  }; \
  template<class Class> \
  struct HasMember_##NAME<Class, void> \
  { \
    typedef char (&yes)[2]; \
    template<unsigned long> struct exists; \
    template<typename V> static yes Check (exists<sizeof(&V::NAME)>*); \
    template<typename> static char Check (...); \
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
  }

Использование: Просто вызовите макрос с любым членом, которого вы хотите найти:

HasMember(Foo);  // Creates a SFINAE `class HasMember_Foo`
HasMember(i);    // Creates a SFINAE `class HasMember_i`

Теперь мы можем использовать HasMember_X Проверять X в любой class как показано ниже:

#include<iostream>
struct S
{
  void Foo () const {}
//  void Foo () {}  // If uncommented then type should be mentioned in `HasMember_Foo`    
  int i;
};
int main ()
{
  std::cout << HasMember_Foo<S, void (S::*) () const>::value << "\n";
  std::cout << HasMember_Foo<S>::value << "\n";
  std::cout << HasMember_i<S, int (S::*)>::value << "\n";
  std::cout << HasMember_i<S>::value << "\n";
}

Ловит:

  1. В случае методов, если мы не упоминаем тип, то classне должно быть перегруженных методов. Если это так, то этот трюк не удался. т.е. даже если именованный элемент присутствует более одного раза, результат будет false,
  2. Если член является частью базового класса, этот трюк не выполняется; например, если B является основой S & void B::Bar () присутствует, тогда HasMember_Bar<S, void (B::*)()>::value или же HasMember_Bar<S, void (S::*)()>::value или же HasMember_Bar<S>::value дам false

С std::experimental::is_detected а также std::experimental::disjunction Вы могли бы сделать это:

//check for a type member named foo
template <typename T>
using foo_type_t = typename T::foo;

//check for a non-type member named foo
template <typename T>
using foo_non_type_t = decltype(&T::foo);

template <typename T>
using has_foo = disjunction<is_detected<foo_type_t, T>,
                            is_detected<foo_non_type_t, T>>;

Тогда вы бы использовали has_foo<my_class>::value во что угодно.

Вышеописанное будет работать не только для типов и функций-членов, но вы можете легко ограничить это, используя такие черты, как std::is_member_function_pointer а также std::is_member_object_pointer если хочешь.

Чтобы указать необязательный аргумент, вы можете использовать std::experimental::is_detected_exact помощник.

Live Demo


Обратите внимание, что если вы берете реализации вышеупомянутых черт со страниц, на которые я ссылаюсь, вы можете использовать это с C++14. Незначительное изменение в disjunction код позволит вам использовать его в C++11.

Для не-C++17:

В библиотеке Boost Type Traits есть метафункции как для проверки наличия члена с заданным именем, так и для более точного контроля с помощью подписи.

Ниже приведена программа, использующая слегка модифицированныеHAS_MEMBER макрос:

#include <iostream>
#include <type_traits>

#define DECLARE_HAS_MEMBER(__trait_name__, __member_name__)                          \
                                                                                     \
    template <typename __boost_has_member_T__>                                       \
    class __trait_name__                                                             \
    {                                                                                \
        using check_type = ::std::remove_const_t<__boost_has_member_T__>;            \
        struct no_type {char x[2];};                                                 \
        using  yes_type = char;                                                      \
                                                                                     \
        struct  base { void __member_name__() {}};                                   \
        struct mixin : public base, public check_type {};                            \
                                                                                     \
        template <void (base::*)()> struct aux {};                                   \
                                                                                     \
        template <typename U> static no_type  test(aux<&U::__member_name__>*);       \
        template <typename U> static yes_type test(...);                             \
                                                                                     \
        public:                                                                      \
                                                                                     \
        static constexpr bool value = (sizeof(yes_type) == sizeof(test<mixin>(0)));  \
    }

struct foo
{
    int bar(){}
};

struct baz
{};

DECLARE_HAS_MEMBER(has_bar, bar);

int main()
{
    std::cout << has_bar<foo>::value << '\n' << has_bar<baz>::value;
}

Как отмечено на связанной странице, он использует тот факт, что если вы наследуете от двух классов, имеющих членов с одинаковыми именами, попытки использовать это имя станут неоднозначными.

Другие вопросы по тегам