I have just begun dabbling with SFINAE and I am having trouble understanding the syntax behind the most often-used example which appears in various forms, but the idea is to check whether a particular type contains a given member. This particular one is from Wikipedia:
template <typename T> struct has_typedef_foobar
{
typedef char yes[1];
typedef char no[2];
template <typename C> static yes& test(typename C::foobar*);
template <typename> static no& test(...);
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
There are a couple of things I don't get about this:
- What is the argument type of the test() overload that returns "yes"? Is it a pointer? Why the typename keyword used as part of the argument? I've seen it used also for testing if a class has a member of a given type, not just a typedef, and the syntax remains unchanged.
- Sometimes I've seen examples that use test(int C::*). This is even more strange, no idea what member of C are we referencing. If it was a real function with a body, instantiated for a real type, and the argument was named, what would it point to and how would you use it?
template <typename T> func(int T::*arg) { *arg = 1; } struct Foo { int x; } foo; func<Foo>(&foo::x); // something like this? func(&foo::x); // or maybe even like this?
- Is template <typename> with no symbol allowed if it's not being used in the second overload? How is it even a template function then?
- Bonus: Could it be made to check for existence of more than one member at a time?
Most of these questions have nothing to do with SFINAE:
typename
. SinceC
is a template parameter totest()
, clearlyC::foobar
is a dependent name. Even though function declarations require a type in front of each argument, the language requires the use oftypename
to turn the dependent nameC::foobar
into a type. With that,typename C::foobar
is just a type and applying a the type constructor*
to it, produces the corresponding pointer type.int C::*
is an unnamed pointer to data member of typeint
.template
can be omitted if it isn't used. Most of the time it is used in some form, though, in which case it is, obviously, required.This example of SFINAE relies on the fact that a function whose parameter list is
...
is the least preferred when doing overload resolution.So first, the compiler will try the
By substituting
C
for the real type. IfC
has a member type namedfoobar
, it will compile and be chosen. If not, it will fail to compile, and the...
overload will be chosen. It will always compile. So to answer your first question, the type that returnsyes&
is whatever has a member typefoobar
.The word
typename
is required for dependent types: types that depend on a template parameter. Because these types could be either variable names or type names, the compiler assumes it's a variable name unless you tell it otherwise withtypename
. It theoretically could check for itself, but that would make the compiler even more complicated to write, which is apparently undesirable enough not to do it.As for your second question, that's a member variable pointer. You can also have member function pointers whose full form is actually like
void test(int(C::*arg_name)())
.As for three, yes, it is allowed but the template argument is never used. Since you're not using deduction, but explicitly specifying the parameter, it's fine. Just like an unnamed normal argument like
void f(int);
.As for four, yes it can, but for the way I know of, you just have to have n * 2 functions for n members that you want to test for. For two, it would look like
1)
typename
is needed in templates where the parameter is a dependent type of one of the template parameters, in this caseC::foobar
is a dependent type of the parameterC
. The argument type of test() is a pointer to a C::foobar. If C::foobar is anything but a type, that version of the test overload will fail to compile and another version of the overload will be found.uk4321 covered the rest.