Мотивация
Я и компания работаем над автоматическим построением 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 с использованием различных трюков с инъекциями друзей или некоторых функций значений / шаблонов, которые я пропустил.