代码之家  ›  专栏  ›  技术社区  ›  Alexey Malistov

你的基础是什么?

  •  113
  • Alexey Malistov  · 技术社区  · 16 年前

    下面的代码是如何工作的?

    typedef char (&yes)[1];
    typedef char (&no)[2];
    
    template <typename B, typename D>
    struct Host
    {
      operator B*() const;
      operator D*();
    };
    
    template <typename B, typename D>
    struct is_base_of
    {
      template <typename T> 
      static yes check(D*, T);
      static no check(B*, int);
    
      static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
    };
    
    //Test sample
    class Base {};
    class Derived : private Base {};
    
    //Expression is true.
    int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
    
    1. B 是私人基地。这是怎么回事?

    2. 请注意 operator B*() 是常量。为什么重要?

    3. 为什么是 template<typename T> static yes check(D*, T); static yes check(B*, int);

    注意 :它是的简化版本(删除宏) boost::is_base_of . 这适用于范围广泛的编译器。

    5 回复  |  直到 8 年前
        1
  •  110
  •   T.C. Yksisarvinen    11 年前

    如果他们是相关的

    让我们假设一下 B D . 那就给我打电话 check ,两个版本都是可行的,因为 Host D* B* 13.3.3.1.2 Host<B, D> B类* 分别。为了找到可以转换类的转换函数,将为第一个类合成以下候选函数 检查 13.3.1.5/1

    D* (Host<B, D>&)
    

    第一个转换函数不是候选函数,因为 无法转换为 .

    对于第二个函数,存在以下候选函数:

    B* (Host<B, D> const&)
    D* (Host<B, D>&)
    

    *this 对象(对象) )由 13.3.3.2/3b1sb4 用于转换为 B类* 功能。

    如果你愿意的话 警官,我们有以下候选人

    B* (Host<B, D>&)
    D* (Host<B, D>&)
    

    这就意味着我们不能再按常量选择了。在普通的重载解析场景中,调用现在是不明确的,因为通常返回类型不会参与重载解析。但是,对于转换函数,有一个后门。如果两个转换函数都一样好,那么它们的返回类型将根据 13.3.3/1 . 因此,如果您删除const,那么将使用第一个,因为 B类* 更好地转化为 B类* D级* .

    现在什么用户定义的转换序列更好?是第二次检查还是第一次检查?规则是,用户定义的转换序列只有在它们使用相同的转换函数或构造函数时才能进行比较 13.3.3.2/3b2 常数 因为它强制编译器执行第二个转换函数。

    13.3.3.2/3b2 ). 在这种情况下, D级* 更好地转化为 D级* 而不是 . 因此,第一个函数被选中,我们承认继承!

    注意,既然我们不需要 事实上 转换为基类,我们就可以识别 因为我们能否从 D级* 不依赖于继承的形式 4.10/3

    如果他们没有关系

    现在我们假设它们之间没有继承关系。因此,对于第一个函数,我们有以下候选函数

    D* (Host<B, D>&) 
    

    现在我们又有了另一套

    B* (Host<B, D> const&)
    

    因为我们不能转换 B类* 如果我们没有继承关系,那么现在两个用户定义的转换序列之间就没有公共的转换函数了!因此,我们将 如果不是因为第一个函数是一个模板。当有一个非模板函数根据不同的条件同样好时,模板是第二选择 13.3.3/1 B D !

        2
  •  24
  •   Evg    6 年前

    让我们看看这些步骤来了解它是如何工作的。

    sizeof(check(Host<B,D>(), int())) 部分。编译器可以很快看到 check(...) check template <typename T> yes check(D*, T); no check(B*, int); . 如果第一个被选中,你会得到 sizeof(yes) sizeof(no)

    接下来,让我们看一下重载分辨率。第一个重载是模板实例化 check<int> (D*, T=int) 第二个候选人是 check(B*, int) Host<B,D> int() . 第二个参数显然无法区分它们;它只是让第一个重载成为一个模板重载。稍后我们将了解为什么模板部分是相关的。

    现在看看需要的转换序列。对于第一次过载,我们有 Host<B,D>::operator D* -一个用户定义的转换。第二,超载更为棘手。我们需要一个B*,但可能有两个转换序列。一个是via Host<B,D>::operator B*() const Host<B,D>::operator D*() + D*->B* 存在。现在假设D确实继承了B。这两个转换序列是 Host<B,D> -> Host<B,D> const -> operator B* const -> B* Host<B,D> -> operator D* -> D* -> B*

    所以,对于相关的B和D, no check(<Host<B,D>(), int()) 会模棱两可。因此,模板 yes check<int>(D*, int) 被选中。但是,如果D不是从B继承的,那么 不是模棱两可的。此时,基于最短转换序列的重载解析无法发生。然而,给定相等的转换序列,重载解析优先于非模板函数,即。 no check(B*, int) .

    您现在明白了为什么继承是私有的并不重要了:这种关系只会消除 no check(Host<B,D>(), int()) 在访问检查发生之前从重载解析。你也明白为什么 operator B* const Host<B,D> -> Host<B,D> const 第一步,没有歧义,还有 不检查(B*,int) 总是被选中的。

        3
  •  5
  •   Matthieu M.    16 年前

    这个 private 位完全被忽略 is_base_of 因为重载解析发生在可访问性检查之前。

    您可以简单地验证这一点:

    class Foo
    {
    public:
      void bar(int);
    private:
      void bar(double);
    };
    
    int main(int argc, char* argv[])
    {
      Foo foo;
      double d = 0.3;
      foo.bar(d);       // Compiler error, cannot access private member function
    }
    

    B 是私有基不会阻止进行检查,它只会阻止转换,但我们从不要求实际转换;)

        4
  •  2
  •   sellibitze    16 年前

    这可能与偏序w.r.t.过载解析有关。如果D来自B,则D*比B*更专业化。

    具体细节相当复杂。你必须找出各种过载解决规则的先例。偏序就是其中之一。转换序列的长度/种类是另一种。最后,如果两个可行的函数被认为是同样好的,则选择非模板而不是函数模板。

    至于私有继承:代码从不要求从D*到B*的转换,这需要公共继承。

        5
  •  0
  •   Hertz    12 年前

    接下来是第二个问题,请注意,如果不是const,如果用B==D实例化,则Host的格式将是错误的。但是is\ base\ of的设计使得每个类都是它自己的一个基,因此其中一个转换运算符必须是const。