Reputation: 6478
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:
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?
Upvotes: 3
Views: 1741
Reputation: 1078
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
static yes& test(typename C::foobar*);
By substituting C
for the real type. If C
has a member type named foobar
, 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 returns yes&
is whatever has a member type foobar
.
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 with typename
. 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
template <typename C> static yes& test1(typename C::foobar*);
template <typename> static no& test1(...);
template <typename C> static yes& test2(typename C::quux*);
template <typename> static no& test2(...);
static const bool value = sizeof(test1<T>(0)) + sizeof(test2<T>(0)) == sizeof(yes) * 2;
Upvotes: 3
Reputation: 153840
Most of these questions have nothing to do with SFINAE:
typename
. Since C
is a template parameter to test()
, clearly C::foobar
is a dependent name. Even though function declarations require a type in front of each argument, the language requires the use of typename
to turn the dependent name C::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 type int
.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.Upvotes: 5
Reputation: 1592
1) typename
is needed in templates where the parameter is a dependent type of one of the template parameters, in this case C::foobar
is a dependent type of the parameter C
. 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.
Upvotes: 0