Как проверить, является ли метод 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)>

Live Demo