这个has_member类模板如何工作?

时间:2012-02-02 18:12:02

标签: c++ sfinae template-meta-programming

我正在尝试了解以下类模板的工作原理(取自here),但我无法理解它:

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))); 
};

更具体地说,我不理解BaseMixin的目的以及operator()的存在。此外,由于Base是从它派生的,我也不理解它。

更具体地说,当模板参数Type定义operator()时,为什么然后触发SFINAE,导致第一个deduce()函数被忽略,选择第二个?


无论如何,这是我的测试代码:

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;
}

输出(ideone):

false
true
true

2 个答案:

答案 0 :(得分:8)

  • BaseMixin有一个operator()定义。
  • Base来自TypeBaseMixin,因此如果Typeoperator(),则Base::operator()上的名称查找将不明确。
  • deduce的调用是Base*,而不是Type*
  • Helper<void (BaseMixin::*)(), &U::operator()>仅在&U::operator()明确解析为BaseMixin::operator()时才会实例化。
  • 相反,如果Helper<void (BaseMixin::*)(), &U::operator()> 实例化,那是因为Typeoperator()上有&U::operator()个名称查找不明确,因此选择deduce返回类型yes的重载。

关于第二个项目的标准引用 - C ++11§10.2/ 2-6:

  

2以下步骤定义类范围f中成员名C的名称查找结果。

     

3 fC查找集,名为 S(f,C),由两个组件集组成:< em>声明集,一组名为f的成员;和子对象集,这是一组子对象,其中找到了这些成员的声明(可能包括 using-declaration )。在声明集中, using-declaration 被它们指定的成员替换,类型声明(包括注入类名称)被它们指定的类型替换。 S(f,C)的计算方法如下:

     

4如果C包含名称f的声明,则声明集包含f中声明的C声明,满足语言结构的要求。查找发生的位置。 [注意: elaborated-type-specifier base-specifier 中查找名称,例如,忽略所有非类型声明,查找嵌套名称说明符中的名称时忽略函数,变量和枚举器声明。另一个例子是,在 using-declaration 中查找名称包括一个类或枚举的声明,该声明通常会被同一范围内该名称的另一个声明隐藏。 - 结束注释]如果结果声明集不为空,则子对象集本身包含C,计算完成。

     

5否则(即C不包含f的声明或结果声明集为空), S(f,C)最初为空。 如果C有基类,请在每个直接基类子对象 B i 中计算f的查找集,并合并每个查找集 S(f,B i 依次进入 S(f,C)

     

6以下步骤定义将查找集 S(f,B i 合并到中间 S(f,C):

     
      
  • 如果 S(f,B i 的每个子对象成员是 S的至少一个子对象成员的基类子对象( f,C),或者如果 S(f,B i 为空, S(f,C)不变合并完成。相反,如果 S(f,C)的每个子对象成员都是 S的子对象成员的基类子对象(f,B i ,或者如果 S(f,C)为空,则新 S(f,C) S的副本( F,B <子> I
  •   
  • 否则,如果 S(f,B i S(f,C)的声明集不同,合并是不明确的:新的 S(f,C)是一个具有无效声明集和子对象集合的查找集。在后续合并中,无效的声明集是被认为与众不同。
  •   
  • 否则,新的 S(f,C)是一个包含共享声明集和子对象集合的查找集。
  •   

答案 1 :(得分:2)

Base可以从operator()Type继承BaseMixin,如果Typeoperator()则隐藏BaseMixin'运营商。

因此,如果Type未定义operator (),则Base的{​​{1}}可以隐式投放到operator()的{​​{1}}。然后,BaseMixinoperator()匹配。

相反,如果deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0)定义了U==Base,则Type的{​​{1}}无法投放到operator()的{​​{1}},Base不会匹配。