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

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

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

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

Ответ 1

С++ 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 в ANY 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";
}

Уловы:

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

Ответ 2

С 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


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

Ответ 3

Для не-С++ 17:

Библиотека свойств типа Boost имеет метафайлы для проверки того, присутствует ли член с данным именем или более мелкозернистый элемент управления, давая подпись.

Ниже приведена программа с использованием слегка измененного макроса 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;
}

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