Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11: Abstracting over const, volatile, lvalue reference, and rvalue reference qualified member function pointers?

C++03 lets you qualify function parameters as being const, volatile, and/or lvalue references (&).

C++11 adds one more: rvalue references (&&).

Furthermore, C++ lets you overload functions based on the qualifiers of their parameters, so that the most appropriate overload is selected when calling the function.

A member function can conceptually be thought of as a function which takes an extra parameter, whose type is a reference to an instance of the class of which it is a member. It's possible to overload a member function based on the qualifiers of this 'extra parameter' in much the same way as any other parameter. This is expressed by putting the qualifiers at the end of the function signature:

struct Foo
{
    int& data();             // return a non-const reference if `this` is non-const
    const int& data() const; // return a const reference if `this` is const
};

In C++03, const and volatile qualifiers are possible, and C++11 also allows & and && (& could theoretically have been allowed in C++03, but it wasn't).

Any combination of qualifiers can be used, with the exception that & and && are mutually exclusive, which makes for 2^2 = 4 possibilities in C++03 and 2^4-4 = 12 in C++11.

This can be quite a pain when you want to work with member function pointers, because they aren't even a little bit polymorphic in these qualifiers: the qualifiers on the "this type" of a member function pointer passed as an argument must exactly match those on the type of the parameter it's being passed as. C++ also offers no explicit facility to abstract over qualifiers. In C++03 this was mostly OK, because you would have to write a const version and a non-const version and no one cares about volatile, but in the pathological case in C++11 (which is not as uncommon as it is pathological) you could have to manually write as many as 12 overloads. Per function.

I was very happy to discover that if you are passing the type of the enclosing class as a template parameter and derive the type of a member function pointer from it, that const and volatile qualifiers are allowed and propagated as you would expect:

template<typename Object>
struct Bar
{
    typedef int (Object::*Sig)(int);
};

Bar<Baz>;                // Sig will be `int (Baz::*)(int)`
Bar<const Baz>;          // Sig will be `int (Baz::*)(int) const`
Bar<volatile Baz>;       // Sig will be `int (Baz::*)(int) volatile`
Bar<const volatile Baz>; // Sig will be `int (Baz::*)(int) const volatile`

This is a great deal nicer than having to write out all of the cases manually.

Unfortunately, it doesn't seem to work for & and &&.

GCC 4.7 says:

error: forming pointer to reference type ‘Baz&&’

But that's not too surprising, given that GCC as of 4.7 doesn't yet have support for reference qualifiers on this.

I also tried it with Clang 3.0, which does have such support:

error: member pointer refers into non-class type 'Baz &&'

Oh, well.

Am I correct in concluding that this is not possible, and that there's no way to abstract over reference qualifiers on the "this type" of member function pointers? Any other techniques for abstracting over qualifiers (especially on this) other than in the specific case when you're passing the "this type" as a template parameter would also be appreciated.

(It's worth pointing out that if C++ didn't distinguish between member functions and normal functions, this would all be trivial: you'd use the template parameter as the type of a parameter of the function (pointer), and the template argument would be passed through as-is, qualifiers intact, no extra thought necessary.)

like image 341
glaebhoerl Avatar asked Jan 15 '12 19:01

glaebhoerl


1 Answers

Have you thought about simply specializing your template ?

You can just add the two versions:

template <typename Object>
struct Bar<Object&> {
  typedef int (Object::*Sig)(int)&;
};

template <typename Object>
struct Bar<Object&&> {
  typedef int (Object::*Sig)(int)&&;
};

And then the compiler will pick the right specialization (or fallback to the general case) appropriately.

This saves you from the const/volatile thing, but does imply that you need to write the code 3 times.

like image 150
Matthieu M. Avatar answered Oct 16 '22 14:10

Matthieu M.