I was looking at the implementation of the is_class
template in Boost, and ran into some syntax I can't easily decipher.
template <class U> static ::boos开发者_运维知识库t::type_traits::yes_type is_class_tester(void(U::*)(void));
template <class U> static ::boost::type_traits::no_type is_class_tester(...);
How do I interpret void(U::*)(void)
above? I'm familiar with C, so it appears somewhat analogous to void(*)(void)
, but I don't understand how U::
modifies the pointer. Can anyone help?
Thanks
*
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;
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)();
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.
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.
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:
- Those function templates are not really function templates. They are just forward declarations of a pair of overloaded function templates. The function templates themselves are never defined. Seeing that this is so and understanding how this works without needing the templates to ever be defined is one of the key elements in understanding this construct.
- The answers that talked about how to use this first function template missed the boat. You can't use this function template because its definition doesn't exist.
- Note that thanks to that arcane argument, the first of the two function templates make sense only when the type
T
is a class. Fundamental types and pointers don't have member functions. The first declaration is invalid syntax for non-class types. - Contrast that to the second of the overloaded function templates, which is valid syntax for all template parameters, and the function (if it existed) would take any arguments thrown at it thanks to its variadic ... argument. (Aside: This is vaguely reminiscent of my favorite one-line C program that can solve any problem in the world, given properly formatted user input.)
- While the function template declarations aren't usable as functions, the declarations can be used in simple compile-time queries such as a query about the return type. The actual definition isn't needed for this kind of query. Only the prototype is needed.
- This is exactly what the class template
IsClass
does to define the compile-time constantIsClass<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.
精彩评论