I'm trying to understand how the following class template works (taken from here), but I couldn't understand it properly:
template <typename Type>
class has_member
{
class yes { char m;};
class no { yes m[2];};
struct BaseMixin
{
void operator()(){}
};
struct Base : public Type, public BaseMixin {};
template <typename T, T t> class Helper{};
template <typename U>
static no deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0);
static yes deduce(...);
public:
static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0)));
};
More specifically, I don't understand the purpose of BaseMixin
and the presence of operator()
in it. Also, since Base
is derived from it, I don't understand it as well.
Even more specifically, when template parameter Type
has defined operator()
, why only then SFINAE is triggered, causing the first deduce()
function to be ignored and the second one is chosen?
Anyway, this is my test code:
struct A{}; //SFINAE is triggered for A
struct B{ void operator()(){} }; //SFINAE is not triggered for B
struct C{ void operator()(int,int){} }; //SFINAE is not triggered for C
int main()
{
std::cout << std::boolalpha; //enable true/false instead of 1/0!
std::cout << has_member<A>::result << std::endl;
std::cout << has_member<B>::result << std::endl;
std::cout << has_member<C>::result << std::endl;
}
Output(ideone):
false
true
true
BaseMixin
has an operator()
definition.Base
derives from both Type
and BaseMixin
, so if Type
has an operator()
then name lookup on Base::operator()
will be ambiguous.deduce
is called for Base*
, not Type*
.Helper<void (BaseMixin::*)(), &U::operator()>
will only instantiate if &U::operator()
unambiguously resolves to BaseMixin::operator()
.Helper<void (BaseMixin::*)(), &U::operator()>
does not instantiate, it's because Type
has its own operator()
making name lookup on &U::operator()
ambiguous, and consequently the overload of deduce
returning type yes
is chosen.Standard citation regarding the second bullet — C++11 §10.2/2-6:
2 The following steps define the result of name lookup for a member name
f
in a class scopeC
.3 The lookup set for
f
inC
, called S(f,C), consists of two component sets: the declaration set, a set of members namedf
; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including injected-class-names) are replaced by the types they designate. S(f,C) is calculated as follows:4 If
C
contains a declaration of the namef
, the declaration set contains every declaration off
declared inC
that satisfies the requirements of the language construct in which the lookup occurs. [ Note: Looking up a name in an elaborated-type-specifier or base-specifier, for instance, ignores all non-type declarations, while looking up a name in a nested-name-specifier ignores function, variable, and enumerator declarations. As another example, looking up a name in a using-declaration includes the declaration of a class or enumeration that would ordinarily be hidden by another declaration of that name in the same scope. —end note ] If the resulting declaration set is not empty, the subobject set containsC
itself, and calculation is complete.5 Otherwise (i.e.,
C
does not contain a declaration off
or the resulting declaration set is empty), S(f,C) is initially empty. IfC
has base classes, calculate the lookup set forf
in each direct base class subobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).6 The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C):
- If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject members of S(f,C), or if S(f,Bi) is empty, S(f,C) is unchanged and the merge is complete. Conversely, if each of the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), or if S(f,C) is empty, the new S(f,C) is a copy of S(f,Bi).
- Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.
- Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets.
Base
can inherit operator()
from Type
, or from BaseMixin
and if Type
has operator()
it hides BaseMixin
's operator.
So, if Type
has no operator ()
defined, Base
's operator()
can be casted implicitly to BaseMixin
's operator()
. Then, deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0)
with U==Base
will match.
Oppositely, if Type
has operator()
defined, Base
's operator()
cannot be casted to BaseMixin
's one, so deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0)
will not match.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With