I was looking at the implementation of the is_class
template in Boost, and ran into some syntax I can\'t easily decipher.
template
*
indicates a pointer, because you can access its contents by writing *p
. U::*
indicates a pointer to a member of class U
. You can access its contents by writing u.*p
or pu->*p
(where u
is an instance of U
).
So, in your example, void (U::*)(void)
is a pointer to a member of U
that is a function taking no arguments and returning no value.
Example:
class C { void foo() {} };
typedef void (C::*c_func_ptr)(void);
c_func_ptr myPointer = &C::foo;
It's a pointer to a member function of class U. it's quite similar to
void(*)(void)
but it points to a member function of class U.
I was going to ask this very same question, but it has already been asked. Unfortunately, none of the existing answers really answered the question. Not for me at least. I had to puzzle it out. I wanted to ask the same question as the OP, plus some. My question: WTF is this is_class_tester (void (U::*)(void))
stuff, and how does this construct work in the context of SFINAE (Substitution Failure Is Not An Error)?
With some simplification, Boost uses the construct as follows:
template <typename U>
char is_class_tester (void (U::*)(void));
template <typename U>
TypeBiggerThanChar is_class_tester (...);
template <typename T>
struct IsClass {
static const bool value = sizeof (is_class_tester<T>(0)) == 1;
};
Some observations:
T
is a class. Fundamental types and pointers don't have member functions. The first declaration is invalid syntax for non-class types.IsClass
does to define the compile-time constant IsClass<SomeType>::value
.So how does IsClass<SomeType>::value
gets its value, and how does it do that at compile time? The compiler has to either make sense of sizeof (is_class_tester<T>(0))
or give up trying. We need to look at two cases based on whether the type SomeType
is or is not a class.
Case 1: SomeType
is a class.
Here both template declarations are valid syntax, so the compiler has two viable candidates to choose from. The compiler can and must the first function template because the selection rules dictate that a variadic function gets lowest priority in the selection. This selected function returns a char. Since sizeof(char) is guaranteed to be 1, IsClass<SomeType>::value
will be true in the case that SomeType
is a class.
Case 2: SomeType
is not a class.
Here is where SFINAE kicks in. The first function template declaration is invalid syntax. The compiler can't just give up here because of SFINAE. It has to keep looking for an alternative, and the second function template declaration fits the bill. The only viable function returns a TypeBiggerThanChar
, definition elided, but hopefully obvious. All we want is sizeof() this thing. It's bigger than a char, so IsClass<SomeType>::value
will be false in the case that SomeType
is not a class.
You're right, it is analogous to a function pointer. Rather, this is a pointer to member function, where the member is of the class U
.
The difference in type is necessitated because member functions have an implicit this
pointer, as they cannot be called without an instance. Getting rid of the template might make it a bit easier:
struct foo
{
void bar(void);
};
void(*)(void)
won't do, as this has no way to communicate an instance of the class. Rather, we need:
void (foo::*)(void)
Indicating that this function pointer requires an instance of foo
.
For what it's worth, you use them like this:
typedef void (foo::*func_ptr)(void);
foo f;
foo* fp = &f;
func_ptr func = &foo::bar;
(f.*func)();
(fp->*func)();
Start from U, then work inside out.
The declared type is a pointer to a member function from class U taking void arguments and returning void.