标准库中std::is_base_of的可能实现解释

5
1.  template <typename Base> std::true_type is_base_of_test_func( Base* );
2.  template <typename Base> std::false_type is_base_of_test_func( void* );
3.  template <typename Base, typename Derived>
    using pre_is_base_of = decltype( is_base_of_test_func<Base>( std::declval<Derived*>() ) );

4.  template <typename Base, typename Derived, typename = void>
    struct pre_is_base_of2 : public std::true_type {};

5.  template<typename ...> using void_t = void;
6.  template <typename Base, typename Derived>
    struct pre_is_base_of2<Base, Derived, void_t<pre_is_base_of<Base, Derived>>> : public pre_is_base_of<Base, Derived>{};


7.  template <typename Base, typename Derived>
    struct is_base_of : public std::conditional_t<std::is_class<Base>::value && std::is_class<Derived>::value,
                                                  pre_is_base_of2<Base, Derived>,
                                                  std::false_type>
    {
    };

第1行和第2行都很简单明了。

然而,第3行中的using非常模糊,因为我不能简单地将每个pre_is_base_of的出现替换为其定义。因此,using并不是文档所说的那样。它还涉及一些信仰问题。如果我没记错的话,pre_is_base_of的使用应该返回std::true_typestd::false_type

当涉及到void_t时,我也很茫然。这一行会产生什么魔法效果?
pre_is_base_of2的两个实现中,都应该采用3种类型吗?第6行继承有什么意义?可能还有更多问题,但我们先停止吧。

我需要对这里涉及到的魔法进行详细解释。基本上,我正在尝试理解代码如何工作。

编辑:当default问我错误在哪里时,我替换了每个pre_is_base_of的出现,现在没有错误了。


为什么我不能简单地用pre_is_base_of的定义替换每个出现的实例? - cpplearner
什么错误? - default
你读了哪些文档? - default
你指的“religion”是什么意思? - default
3
我相信这个 is_base_of 的实现是从 http://en.cppreference.com/w/cpp/types/is_base_of 复制过来的。那个实现的作者在相应的讨论页中给出了一些解释。 - cpplearner
显示剩余5条评论
2个回答

12
  1. template <typename Base> std::true_type is_base_of_test_func( Base* );

当参数是Base类型或派生自Base类型时,此重载具有最高优先级。

  1. template <typename Base> std::false_type is_base_of_test_func( void* );

此重载将匹配任何类型,具有最低优先级。

  1. template <typename Base, typename Derived> using pre_is_base_of = decltype( is_base_of_test_func<Base>( std::declval<Derived*>() ) );

使用指向Derived的指针调用is_base_of_test_func函数返回的类型将成为pre_is_base_of类型。如果Derived从Base继承,则它将返回std::true_type,否则void*重载将被选择并返回std::false_type。现在我们已将函数调用结果转换为类型。

  1. template <typename Base, typename Derived, typename = void> struct pre_is_base_of2 : public std::true_type {};

通用情况下,这将是一个true_type。由于第三个模板参数是默认的,因此当没有创建其他专业化时,这将是类定义的版本。

  1. template<typename ...> using void_t = void;

这是一个更简单的启用enable_if的方法。只有当X是合法类型时,void_t<X>才是一个类型。

  1. template <typename Base, typename Derived> struct pre_is_base_of2<Base, Derived, void_t<pre_is_base_of<Base, Derived>>> : public pre_is_base_of<Base, Derived>{};

如果void_t是一个合法类型(即pre_is_base_of<Base>(Derived*)是一个有效的表达式),这将是pre_is_base_of2的专业化,它将评估调用上面的测试函数的decltype。仅在pre_is_base_of<Base,Derived>是有效类型时(即存在对测试函数的调用)才会选择它。

本质上这段代码的意思是:

  1. template <typename Base, typename Derived> struct is_base_of : public std::conditional_t<std::is_class<Base>::value && std::is_class<Derived>::value, pre_is_base_of2<Base, Derived>, std::false_type> { };
IF Base and Value are classes AND void_t<decltype(is_base_of_test_func<Base>(Derived*))> is a type
THEN
    select the type of pre_is_base_of2<Base, Derived, void_t<...is the expression legal?...>>
ELSE
    select false_type        

更新:

希望这个小演示程序能够提供一些明确的解释:

#include <type_traits>
#include <iostream>

template<class...> using void_t = void;

// this expands in any case where no second type is provided
template<class T, typename = void> struct does_he_take_sugar : std::false_type {};

// the specialisation can only be valid when void_t<expr> evaluates to a type.
// i.e. when T has a member function called take_sugar
template<class T> struct does_he_take_sugar<T, void_t<decltype(std::declval<T>().take_sugar())>> : std::true_type {};


struct X {
    int take_sugar();
};

struct Y {
    int does_not();
};

int main()
{

    // X::take_sugar is a function therefore void_t<decltype(...X)> will evaluate to void
    std::cout << does_he_take_sugar<X>::value << std::endl;

    // Y::take_sugar is not a function therefore void_t<decltype(...Y)> will not evaluate at all
    std::cout << does_he_take_sugar<Y>::value << std::endl;

    // int::take_sugar is not even valid c++ void_t<decltype(...int)> will not evaluate at all
    std::cout << does_he_take_sugar<int>::value << std::endl;
}

你澄清了一些事情,谢谢。关于第6点,如果void_t不是有效的类型,那么将返回什么类型?哪个特化将处理它? - Malik Urac
@MalikUrac 没有。该专业将通过SFNIE失败。 - Richard Hodges
让我重新表述一下。哪段代码处理了这个替换失败?我仍然不清楚我们如何使用2个和3个模板参数来实现pre_is_base_of2?第三个参数有一个默认类型void。如果void_t返回int会发生什么? - Malik Urac
@MalikUrac 中的 void_t<X...> 总是会解析为伪类型 void,或者根本无法解析(错误情况)。如果它根本无法解析,则取决于它的任何模板扩展都不会发生(SFNAE)。在表达式中 MyTemplate<void_t<X>>,如果 void_t<X> 无效,则不会有扩展。 如果有默认值可用,例如: template class MyTemplate<typename = void>{};,则将使用这个默认值。 - Richard Hodges
@MalikUrac添加了一个小演示,希望能帮助您理解如何选择特化。 - Richard Hodges
显示剩余5条评论

0

一个可能实现的框架。目前的形式没有考虑POD类型。

template <class Base, class Derived, class = void>
struct is_base_of
{
    static const bool value = false;
};

template <class Base, class Derived>
struct is_base_of<Base, Derived, typename enable_if<(Base*)(Derived*)nullptr == (Derived*)nullptr, void>::type>
{
    static const bool value = true;
};

这个比较很巧妙:(Base*)(Derived*)nullptr == (Derived*)nullptr。只有当Derived继承自Base时,比较才是有效的,否则表达式无效并导致SFINAE。

1
如果Base从Derived继承,这也是有效的,但这可能不是预期的行为。 - N. Shead
@N. Shead 谢谢。我已经为“基类继承派生类”的情况添加了修复。 - Marius D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接