【问题标题】:How to test for presence of an inner class in a class via SFINAE?如何通过 SFINAE 测试类中是否存在内部类?
【发布时间】:2012-08-22 06:15:49
【问题描述】:

我正在尝试为具有特定名称的内部类的类提供不同的模板特化。我从here 中获得了线索并尝试了以下方法:

#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)。有人能解释一下这里发生了什么吗?专业化流程在这里如何运作?

【问题讨论】:

  • 为什么不简单地使用 enable_if?
  • @ForEveR:我对这东西背后的操作理论很感兴趣。

标签: c++ template-specialization sfinae


【解决方案1】:

回答最初的问题

您在此处定义的模板特化:

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

当有人将数据类型HasXYZ&lt;A,A::XYZ&gt;用于某些数据类型A时才会生效。

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

因此,当您使用HasXYZ&lt;Foo&gt; 时,它会回退到使用默认模板参数void 作为第二个参数。

不用说,如果你明确地使用HasXYZ&lt;Foo,Foo:XYZ&gt;,你会得到预期的输出。但这显然不是你想要的。

恐怕获得您需要的唯一方法是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。但是 specialization(上面的第二个定义)定义了 class A 在第二个模板参数真的 void 时的实际样子。

这意味着如果你在代码中使用A&lt;int&gt;,默认参数将被补充,所以你得到A&lt;int,void&gt;,然后编译器会找到最合适的模板特化,也就是第二个以上。

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

这就是为什么在你的例子中,当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 帖子也提到了这种机制:

This replyTemplate specialization to use default type if class member typedef does not exist

Default values of template parameters in the class template specializations

【讨论】:

  • 您的答案似乎是正确的答案,但我认为我在这里仍然缺少一些东西。我已将问题编辑为更具体。你能编辑你的来澄清为什么一个类型定义的类型是依赖的,但内部类不是吗?
  • @dragonroot:我已经为问题的typedef 部分添加了答案。
【解决方案2】:

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

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

【讨论】:

  • 谢谢,但我仍然很想知道为什么我尝试的方法不起作用。
【解决方案3】:

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

有关其工作原理的说明,请参阅this question

【讨论】:

  • 哦,所以它必须匹配初始模板声明中给出的void?现在我迷路了。在这种情况下,整个专业化如何工作,我最初有两个 args,第二个默认为 void,而第二个 arg 仍然匹配 void 的专业化?
  • @dragonroot:就像我说的,阅读链接的问题。如果XYZ 存在于T 内部,则void_&lt;...&gt; 部分将不会SFINAE 输出并将产生void,这将使特定的部分特化匹配第二个参数为void 的所有实例化,这要归功于默认情况下,应该始终如此。现在如果XYZ不存在,整个部分规范基本不匹配,会选择基础模板。
  • 是的,但我的问题是,为什么必须是void?如果 XYZ 被输入到int,它将不起作用。为什么?我也(再次)更新了这个问题来问这个问题。
  • @dragonroot:使用我发布的代码,这并不重要,因为void 是由小助手结构提供的。对于您的代码,是的,这很重要。如果T::XYZ 存在并且是void,则部分特化将读取has_XYZ&lt;T, void&gt;。然后,只要您使用 has_XYZ&lt;some_type&gt;,就会应用默认参数,这将导致选择部分特化。如果 T::XYZint,则部分特化将显示为 has_XYZ&lt;T, int&gt;,默认的 void 参数将不再匹配。
  • 为什么只有在第二个参数值与初始模板的默认值匹配时才选择部分特化?我想我的问题更多是关于整个专业选择的工作原理。阅读标准太重了,我真的不知道还能去哪里看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-06
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
相关资源
最近更新 更多