如何通过SFINAE测试类中是否存在内部类

How to test for presence of an inner class in a class via SFINAE?

本文关键字:是否 存在 内部类 测试类 何通过 SFINAE      更新时间:2023-10-16

我正在尝试为具有特定名称的内部类的类提供不同的模板专用化。我从这里得到了一条线索,并尝试了以下方法:

#include <iostream>
template< typename T, typename Check = void > struct HasXYZ
{ static const bool value = false; };
template< typename T > struct HasXYZ< T, typename T::XYZ >
{ static const bool value = true; };
struct Foo
{
  class XYZ {};
};
struct FooWithTypedef
{
  typedef void XYZ;
};
int main()
{
  // The following line prints 1, as expected
  std::cout << HasXYZ< FooWithTypedef >::value << std::endl;
  // The following line prints 0. Why?
  std::cout << HasXYZ< Foo >::value << std::endl;
  return 0;
}

正如您所看到的,如果我在FooWithTypedef中测试typedef定义的类型,它就可以工作了。但是,如果该类型是真正的内部类,则它不起作用。只有当FooWithTypedef中的typedef-ed类型与初始模板声明中第二个参数的默认值(在我的示例中是void)匹配时,它才起作用。有人能解释一下这里发生了什么吗?专业化过程在这里是如何运作的?

回答初始问题

你在这里定义的模板专业化:

template <typename T> struct HasXYZ <T,typename T::XYZ>
{ static const bool value = true; };

当有人将数据类型CCD_ 6用于某个数据类型CCD_。

注意,无论A是什么,A::XYZ都是完全独立于A的数据类型。内部类本身就是数据类型。当您使用A作为第一个模板参数时,编译器绝对没有理由假设您要使用名为A:XYZ的东西作为第二个参数,即使存在该名称的内部类,即使这样做会导致编译器实现与模板参数完全匹配的模板专用化。模板专业化是基于编码器提供的模板参数找到的,而不是基于进一步的可能的模板参数。

因此,当使用HasXYZ<Foo>时,第二个参数将返回到使用默认模板参数void

不用说,如果您显式地使用HasXYZ<Foo,Foo:XYZ>,您将获得预期的输出。但这显然不是你想要的。

恐怕得到你需要的东西的唯一方法是std::enable_if(或者类似的方法)。


附加问题的答案(更新后)

考虑以下简化:

template <typename T, typename Check = void>
struct A
{ static const bool value = false; };
template <typename T>
struct A<T,void>
{ static const bool value = true; };

主定义为第二个模板参数指定默认参数void。但是专门化(上面的第二个定义)定义了如果第二个模板参数真的voidclass A的实际外观。

这意味着,如果你在代码中使用A<int>,默认参数将得到补充,这样你就得到了A<int,void>,然后编译器会找到最合适的模板专用化,这就是上面的第二个。

因此,虽然默认模板参数被定义为主模板声明的一部分,但使用它们并不意味着使用了主模板定义。这主要是因为默认模板参数是模板声明的一部分,而不是模板定义(*)。

这就是为什么在您的示例中,当typedef void XYZ包含在FooWithTypedef中时,第二个模板参数默认为void,然后找到最合适的专业化。即使在模板专用化中,第二个参数被定义为T::XYZ而不是void,这也是有效的。如果在评估时这些内容相同,则将选择模板专用化(§14.4"类型等效")。

(*)我在《标准》中找不到一份真正如此明确的声明。但有一个§14.1/10,它描述了一个模板有多个声明(但只有一个主要定义)的情况:

(§14.1/10)可与模板声明或定义一起使用的默认模板参数集是通过合并定义中的默认参数(如果在范围内)和范围内的所有声明获得的,方法与默认函数参数相同(8.3.6)。[示例:

  template<class T1, class T2 = int> class A;
  template<class T1 = int, class T2> class A;

相当于

  template<class T1 = int, class T2 = int> class A;

]。

这表明默认模板参数背后的机制独立于用于识别模板最合适的专业化的机制。

此外,还有两个现有的SO职位也提到了这一机制:

如果类成员typedef不存在,则此回复模板专用化以使用默认类型

类模板专业化中模板参数的默认值

这里是另一个检测内部类存在的版本:

#include <iostream>
template< typename T >
struct HasXYZ
{
  typedef char                 yes;
  typedef struct{ char d[2]; } no;
  template<typename T1>
  static yes test( typename T1::XYZ * );
  template<typename T1>
  static no test(...);
  static const bool value = ( sizeof( test<T>(0) ) == sizeof( yes ) );
};
struct Foo
{
  class XYZ {};
};
struct Bar
{
  class ABC {};
};
int main()
{
  std::cout << std::boolalpha << HasXYZ< Foo >::value << std::endl;
  std::cout << std::boolalpha << HasXYZ< Bar >::value << std::endl;
}

A::XYZ需要void才能选择部分专用化,而类类型永远不会这样。使其工作的一种方法是使用伪依赖void类型名称:

template<class T>
struct void_{ typedef void type; };
template<class T, class = void>
struct has_XYZ{ static bool const value = false; };
template<class T>
struct has_XYZ<T, typename void_<typename T::XYZ>::type>{
  static bool const value = true;
};

有关如何工作的解释,请参阅此问题。