Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does this has_member class template work?

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
like image 604
Nawaz Avatar asked Dec 27 '22 06:12

Nawaz


2 Answers

  • 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().
  • Conversely, if 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 scope C.

3 The lookup set for f in C, called S(f,C), consists of two component sets: the declaration set, a set of members named f; 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 name f, the declaration set contains every declaration of f declared in C 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 contains C itself, and calculation is complete.

5 Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is initially empty. If C has base classes, calculate the lookup set for f 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.
like image 75
ildjarn Avatar answered Jan 16 '23 09:01

ildjarn


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.

like image 44
Lol4t0 Avatar answered Jan 16 '23 11:01

Lol4t0