Как проверить, является ли метод const?
Как я могу получить логическое значение, указывающее, имеет ли известный метод классификатор const или нет?
Например:
struct A {
void method() const {}
};
struct B {
void method() {}
};
bool testA = method_is_const<A::method>::value; // Should be true
bool testB = method_is_const<B::method>::value; // Should be false
В заголовке type_traits
я нашел тест is_const
, который мог бы использовать, но мне нужен тип метода, и я не уверен, как его получить.
Я пробовал: std::is_const<decltype(&A::method)>::value
, но он не работает, и я могу понять, почему (void (*ptr)() const) != const void (*ptr)()
).
Ответы
Ответ 1
Гораздо проще проверить, может ли функция-член вызываться на const
-qualified lvalue.
template<class T>
using const_lvalue_callable_foo_t = decltype(std::declval<const T&>().foo());
template<class T>
using has_const_lvalue_callable_foo = std::experimental::is_detected<const_lvalue_callable_foo_t, T>;
Промойте и повторите, за исключением std::declval<const T>()
, чтобы проверить, может ли указанная функция вызываться на const
-qualified rvalue. Я не могу думать о хороших вариантах использования для функций-членов const &&
, поэтому существует ли вероятность обнаружения этого случая.
Обратитесь к текущему основам библиотеки 2 рабочего проекта TS о том, как реализовать is_detected
.
Более сложным является проверка того, указывает ли конкретный тип-указатель на тип функции на определенный тип cv-qualifier-seq. Для этого требуется 6 частичных специализаций на cv-qualifier-seq (const
и const volatile
- разные cv-qualifier-seqs) и до сих пор не могут обрабатывать перегруженные функции-члены или шаблоны-члены-члены. Набросок идеи:
template<class T>
struct is_pointer_to_const_member_function : std::false_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args...) const> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args...) const &> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args...) const &&> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args..., ...) const> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args..., ...) const &> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args..., ...) const &&> : std::true_type {};
Если вы хотите, чтобы const volatile
был true
тоже, выделите еще 6 частичных специализаций вдоль этих строк.
Ответ 2
Причина, по которой std::is_const<decltype(&A::method)>::value
не работает, заключается в том, что функция-член const не является const (функцией-членом). Это не const
верхнего уровня, как для const int
vs int
.
void_t
этого мы можем использовать черту типа с использованием void_t
которая проверяет, можем ли мы вызвать method
для const T
:
template <typename... >
using void_t = void;
template <typename T, typename = void>
struct is_const_callable_method : std::false_type { };
template <typename T>
struct is_const_callable_method<T, void_t<
decltype(std::declval<const T&>().method())
> > : std::true_type { };
демонстрация
Ответ 3
В С++ 20 все становится намного проще, потому что концепции стандартизированы, что включает в себя идиому обнаружения.
Теперь все, что нам нужно написать, это ограничение:
template<class T>
concept ConstCallableMethod = requires(const T& _instance) {
{ _instance.method() }
};
ConstCallableMethod
проверяет, что выражение _instance.has_method()
правильно сформировано, учитывая, что _instance
является ссылочным типом const.
Учитывая ваши два класса:
struct A {
void method() const { }
};
struct B {
void method() { }
};
Ограничение будет true
для A
(ConstCallableMethod<A>
) и false
для B
Если вы также хотите проверить, что тип возвращаемого значения функции method
void, вы можете добавить ->void
к ограничению следующим образом:
template<class T>
concept ConstCallableMethodReturnsVoid = requires(const T& _instance) {
{ _instance.method() } -> void
};
Если вы хотите быть немного более универсальным, вы можете передать указатель на функцию-член на концепцию и проверить, может ли этот указатель функции вызываться с экземпляром const
(хотя это становится немного менее полезным при наличии перегрузок):
template<class T, class MemberF>
concept ConstCallableMemberReturnsVoid = requires(const T& _instance, MemberF _member_function) {
{ (_instance.*_member_function)() } -> void
};
Вы бы назвали это так:
ConstCallableMemberReturnsVoid<A, decltype(&A::method)>
Это учитывает некоторый другой теоретический класс, такой как C
, который имеет метод const, но не названный method
:
struct C
{
void foobar() const{}
};
И мы можем использовать ту же концепцию для тестирования:
ConstCallableMemberReturnsVoid<C, decltype(&C::foobar)>