Общий способ доступа к закрытым / защищенным членам

Мотивация

Я и компания работаем над автоматическим построением C ++ SDK для нескольких платформ, включая Windows.
Таким образом, я должен как-то проверить, является ли частный член типа указателей на функции правильным, поскольку LoadLibrary в Windows API используются только имена функций, игнорируя их тип.
Есть некоторые реальные ограничения, поэтому мне нужно получить доступ к закрытым / защищенным членам для тестирования.

Я поискал в Интернете и обнаружил, что это можно сделать с помощью шаблонов, но все примеры не являются общими и не могут быть проверены во время компиляции.

Поэтому я создаю свою собственную версию, которая может быть сделана в более общем плане.
Полный исходный код также можно найти на https://github.com/lackhole/Lupin

Код

К любому члену можно получить доступ при явном создании экземпляра шаблона.
А инъекция друга позволяет вызывать без предоставления класса шаблона, только с помощью соответствующего аргумента (ADL). Итак, нам нужен Accessor класс, который содержит указатель на закрытый член целевого класса, а Accessor должен уметь использовать соответствующие Tag.

template<typename Tag, typename Class, typename Type, Type ptr_>
struct Accessor {
  using class_type                  = Class;
  using access_type                 = Type;
  using pointer_type                = decltype(ptr_);
  using const_pointer_type          = std::add_const_t<pointer_type>;
  static constexpr pointer_type ptr = ptr_;

  friend auto get_accessor_type_impl(Tag) { return Accessor{}; }
};

template<typename Dummy>
struct Tag {
  friend auto get_accessor_type_impl(Tag);
};

Мы должны сохранить функцию друга внутри Accessor поскольку после создания экземпляра с частным указателем мы никогда не сможем повторно получить его тип явно, потому что мы не можем получить доступ к частному указателю.
Таким образом, выше объявлена ​​та же функция в Tag и использовал выведенный возвращаемый тип, только чтобы получить Accessorтип.
C ++ позволяет оставить инъекцию друга внутри класса только в том случае, если тип класса и тип аргумента совпадают.
Это заставляет нас использовать C ++ 14 или выше, где C ++ 11 не может определить возвращаемый тип с помощью auto Только.

Это можно использовать как

using tag_foo_x = Tag<class foo_x>;
template struct Accessor<tag_foo_x, Foo, decltype(&FOO::x), &Foo::x>;

И мы должны получить Accessor<tag_foo_x, ... с участием tag_foo_x, используя приведенную выше функцию друга.

template<typename Tag>
struct TagTraits {
  using tag_type = Tag;
  using accessor_type       = decltype(get_accessor_type_impl(std::declval<tag_type>()));
  using class_type          = typename accessor_type::class_type;
  using access_type         = typename accessor_type::access_type;
  using pointer_type        = typename accessor_type::pointer_type;
};

Теперь мы можем получить Accessorтип, использующий Tag.

using accessor_type = TagTraits<tag_foo_x>::accessor_type;

И теперь все становится намного проще.
Необходимо определить признаки указывающего типа (ptr_тип в Accessor), мы можем использовать несколько вспомогательных шаблонов.

template<typename T>
struct is_function : std::conditional_t<
    std::is_member_function_pointer<std::decay_t<T>>::value ||
    std::is_function<std::remove_pointer_t<T>>::value,
  std::true_type, std::false_type> {};

template<typename T>
struct get_pointing_type {
  using type = T;
};

template<typename T>
struct get_pointing_type<T*> {
  using type = T;
};

template<typename T, typename Class>
struct get_pointing_type<T Class::*> {
  using type = T;
};

template<typename T>
using get_pointing_type_t = typename get_pointing_type<T>::type;

А чтобы получить член, нужно изменить его возвращаемый тип в зависимости от типа указания (переменная или функция).

/** get non-static member variable */

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<!is_function<typename TagTraits<Tag>::access_type>::value,
    get_pointing_type_t<typename TagTraits<Tag>::access_type> &>
get(Target& target) {
  return target.*TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<!is_function<typename TagTraits<Tag>::access_type>::value,
    get_pointing_type_t<typename TagTraits<Tag>::access_type> const &>
get(const Target& target) {
  return target.*TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<!is_function<typename TagTraits<Tag>::access_type>::value,
    get_pointing_type_t<typename TagTraits<Tag>::access_type>>
get(Target&& target) {
  return std::forward<Target>(target).*TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<!is_function<typename TagTraits<Tag>::access_type>::value,
    get_pointing_type_t<typename TagTraits<Tag>::access_type>>
get(const Target&& target) {
  return std::forward<Target>(target).*TagTraits<Tag>::accessor_type::ptr;
}

/** get non-static member function pointer */

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<is_function<typename TagTraits<Tag>::access_type>::value,
    typename TagTraits<Tag>::access_type>
get(Target& target) {
  return TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<is_function<typename TagTraits<Tag>::access_type>::value,
    typename TagTraits<Tag>::access_type const>
get(const Target& target) {
  return TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<is_function<typename TagTraits<Tag>::access_type>::value,
    typename TagTraits<Tag>::access_type>
get(Target&& target) {
  return TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag, typename Target>
inline constexpr
std::enable_if_t<is_function<typename TagTraits<Tag>::access_type>::value,
    typename TagTraits<Tag>::access_type const>
get(const Target&& target) {
  return TagTraits<Tag>::accessor_type::ptr;
}

/** get static member (both variable and function) */

template<typename Tag>
std::enable_if_t<!is_function<typename TagTraits<Tag>::access_type>::value,
    get_pointing_type_t<typename TagTraits<Tag>::access_type> &>
get() {
  return *TagTraits<Tag>::accessor_type::ptr;
}

template<typename Tag>
std::enable_if_t<is_function<typename TagTraits<Tag>::access_type>::value,
    typename TagTraits<Tag>::access_type>
get() {
  return *TagTraits<Tag>::accessor_type::ptr;
}

И некоторая сахарная функция для прямого вызова функции-члена.

/** call non-static member function */

template<typename Tag, typename Target, typename ...Args>
inline constexpr decltype(auto)
call(Target&& target, Args&&... args) {
  using tag_traits = TagTraits<Tag>;
  using access_type = typename tag_traits::access_type;
  using accessor_type = typename tag_traits::accessor_type;

  static_assert(std::is_member_function_pointer<access_type>::value,
                "Tag must represent member function, or perhaps the tag represents static member function");
  return (std::forward<Target>(target).*accessor_type::ptr)(std::forward<Args>(args)...);
}


/** call static member function */

template<typename Tag, typename ...Args>
inline constexpr decltype(auto)
call(Args&&... args) {
  using tag_traits = TagTraits<Tag>;
  using access_type = typename tag_traits::access_type;
  using accessor_type = typename tag_traits::accessor_type;

  static_assert(!std::is_member_function_pointer<access_type>::value &&
                std::is_function<std::remove_pointer_t<access_type>>::value,
                "Tag must represent static member function");
  return (accessor_type::ptr)(std::forward<Args>(args)...);
}

И последний сахар для получения только типа.

template<typename Tag>
struct Type {
  using type = get_pointing_type_t<typename TagTraits<Tag>::access_type>;
};

template<typename Tag>
using Type_t = typename Type<Tag>::type;

В макрос это просто псевдоним для создания тега и явного создания экземпляра шаблона. Использование как показано ниже

#include "access/access.h"

struct hidden {
 private:
  int x = 10;
  std::string str = "hello";
  int func() const &{ return x; }
  int sum(int a, int b) { return a + b; }

  static int sta() { return y; }
  static int y;

};
int hidden::y = 12345;

ACCESS_CREATE_TAG(tag_hidden_x, hidden, x);
ACCESS_CREATE_UNIQUE_TAG(hidden, x);
ACCESS_CREATE_TAG(tag_hidden_str, hidden, str);

ACCESS_CREATE_TAG(tag_hidden_func, hidden, func);
ACCESS_CREATE_TAG(tag_hidden_sum, hidden, sum);
ACCESS_CREATE_TAG(tag_hidden_sta, hidden, sta);
ACCESS_CREATE_TAG(tag_hidden_y, hidden, y);

int main() {
  std::cout << std::boolalpha;

  hidden h;

  std::cout << access::get<tag_hidden_x>(h) << std::endl;
  std::cout << access::get<ACCESS_GET_UNIQUE_TAG(hidden, x)>(h) << std::endl;

  access::get<tag_hidden_x>(h) = 100;
  std::cout << access::get<tag_hidden_x>(h) << std::endl;
  access::get<ACCESS_GET_UNIQUE_TAG(hidden, x)>(h) = 200;
  std::cout << access::get<ACCESS_GET_UNIQUE_TAG(hidden, x)>(h) << std::endl;

  std::cout << access::get<tag_hidden_y>() << std::endl;
  access::get<tag_hidden_y>() = -123;
  std::cout << access::get<tag_hidden_y>() << std::endl;

  access::call<tag_hidden_func>(h);
  (h.*access::get<tag_hidden_func>(h))();

  access::call<tag_hidden_sum>(h, 1, 2);

  access::get<tag_hidden_sta>()();
  access::call<tag_hidden_sta>();

  std::cout << access::get<tag_hidden_str>(h) << std::endl;
  access::get<tag_hidden_str>(std::move(h));
  std::cout << access::get<tag_hidden_str>(h) << std::endl;

  const hidden h2;
  access::get<tag_hidden_x>(h2);

  access::call<tag_hidden_func>(h2);
  (h.*access::get<tag_hidden_func>(h2))();

  std::cout << access::get<tag_hidden_sta>()() << std::endl;
  std::cout << access::call<tag_hidden_sta>() << std::endl;

  static_assert(std::is_same<access::Type_t<tag_hidden_x>, int>::value, "");
  static_assert(std::is_same<access::Type_t<tag_hidden_y>, int>::value, "");

  // Note that the below two type is different. Choose your own way.
  static_assert(std::is_same<access::Type_t<tag_hidden_func>, int () const&>::value, "");                                           // function type
  static_assert(std::is_same<decltype(access::get<tag_hidden_func>(std::declval<hidden>())), int (hidden::*)() const&>::value, ""); // function pointer type

  
  // Note that these two are different too
  static_assert(std::is_same<access::Type_t<tag_hidden_sta>, int()>::value, "");              // function type
  static_assert(std::is_same<decltype(access::get<tag_hidden_sta>()), int(*)()>::value, "");  // function pointer type

  return 0;
}

Заранее благодарим за отзывы!
Есть ли более портативный или лучший способ улучшить это? Например, поддержка C ++ 11 с использованием различных трюков с инъекциями друзей или некоторых функций значений / шаблонов, которые я пропустил.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *