Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test if a method is const?

How can I get a boolean value indicating if a known method has the const qualifier or not?

For example:

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

In the type_traits header I found an is_const test I could use, but I need the method type, and I'm unsure how to obtain that.

I tried: std::is_const<decltype(&A::method)>::value but it doesn't work, and I can understand why (void (*ptr)() const) != const void (*ptr)()).

like image 949
Janito Vaqueiro Ferreira Filho Avatar asked May 23 '15 00:05

Janito Vaqueiro Ferreira Filho


2 Answers

In C++20, things get a lot easier because concepts have been standardized, which subsumes the detection idiom.

Now all we need to write is this constraint:

template<class T>
concept ConstCallableMethod = requires(const T& _instance) {
    { _instance.method() }
};

ConstCallableMethod tests that the expression _instance.has_method() is well formed given that _instance is a const-reference type.

Given your two classes:

struct A {
    void method() const { }
};

struct B {
    void method() { }
};

The constraint will be true for A (ConstCallableMethod<A>) and false for B.


If you wish to also test that the return type of the method function is void, you can add ->void to the constraint like so:

template<class T>
concept ConstCallableMethodReturnsVoid = requires(const T& _instance) {
    { _instance.method() } -> void
};

If you wish to be a little more generic, you can pass in a member function pointer to the concept and test if that function pointer can be called with a const instance (although this gets a little less useful when you have overloads):

template<class T, class MemberF>
concept ConstCallableMemberReturnsVoid = requires(const T& _instance, MemberF _member_function) {
    { (_instance.*_member_function)() } -> void
};

You'd call it like so:

ConstCallableMemberReturnsVoid<A, decltype(&A::method)>

This allows for some other theoretical class like C, that has a const method, but it's not named method:

struct C
{
    void foobar() const{}
};

And we can use the same concept to test:

ConstCallableMemberReturnsVoid<C, decltype(&C::foobar)>

Live Demo

like image 150
AndyG Avatar answered Sep 20 '22 06:09

AndyG


It is a lot simpler to check whether a member function can be called on a 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>;

Rinse and repeat, except with std::declval<const T>(), to check if said function can be called on a const-qualified rvalue. I can think of no good use cases for const && member functions, so whether there's a point in detecting this case is questionable.

Consult the current Library Fundamentals 2 TS working draft on how to implement is_detected.


It is a lot more convoluted to check whether a particular pointer-to-member-function type points to a function type with a particular cv-qualifier-seq. That requires 6 partial specializations per cv-qualifier-seq (const and const volatile are different cv-qualifier-seqs), and still can't handle overloaded member functions or member function templates. Sketching the idea:

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 {};

If you want const volatile to be true too, stamp out another 6 partial specializations along these lines.

like image 31
T.C. Avatar answered Sep 24 '22 06:09

T.C.