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
fin a class scopeC.3 The lookup set for
finC, 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
Ccontains a declaration of the namef, the declaration set contains every declaration offdeclared inCthat 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 containsCitself, and calculation is complete.5 Otherwise (i.e.,
Cdoes not contain a declaration offor the resulting declaration set is empty), S(f,C) is initially empty. IfChas base classes, calculate the lookup set forfin 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