【问题标题】:Is the following template specialization code non-standard or a bug in VS-C++?以下模板专业化代码是非标准的还是 VS-C++ 中的错误?
【发布时间】:2012-03-29 00:23:32
【问题描述】:

以下代码在 GCC 中编译(我使用 ideone,它使用 gcc-4.3.4)但在 Visual Studio 中无法编译。它是标准代码和 Visual C++ 2008 和 2010 中的错误(我都尝试过)还是非标准代码并且 GCC 很乐意编译它?

namespace cool
{
  template <bool, typename = void> struct enable_if {};
  template <typename T> struct enable_if<true, T> { typedef T type; };

  template <typename T0, typename T1> struct is_same { enum { value = false }; };
  template <typename T> struct is_same<T, T> { enum { value = true }; };
}

struct BasePolicy {};
struct BasePolicy2 {};
struct Faz {};

template <typename Policy,
typename = typename cool::enable_if<cool::is_same<BasePolicy, Policy>::value || cool::is_same<BasePolicy2, Policy>::value>::type >
struct Foo;

template <typename Policy>
struct Foo<Policy> {
  Foo();
};

template <typename Policy>
Foo<Policy>::Foo() {
}

int main()
{
  Foo<BasePolicy2> fb;
  // Foo<Faz> fb1;
}

错误 1 ​​错误 C2039: '{ctor}' : is not a member of 'Foo' main.cpp 25

请注意,问题在于Foo 的构造函数的外线定义。如果你在类中定义它,那么 Visual-C++ 很高兴:

template <typename Policy>
struct Foo<Policy> {
  Foo() {}
};

另外,下面的代码在两者上都可以编译(注意 || 和它后面的逻辑都丢失了):

namespace cool
{
  template <bool, typename = void> struct enable_if {};
  template <typename T> struct enable_if<true, T> { typedef T type; };

  template <typename T0, typename T1> struct is_same { enum { value = false }; };
  template <typename T> struct is_same<T, T> { enum { value = true }; };
}

struct BasePolicy {};
struct BasePolicy2 {};
struct Faz {};

template <typename Policy,
  typename = typename cool::enable_if<cool::is_same<BasePolicy, Policy>::value>::type >
struct Foo;

template <typename Policy>
struct Foo<Policy> {
  Foo();
};

template <typename Policy>
Foo<Policy>::Foo() {
}

int main()
{
  Foo<BasePolicy> fb;
  // Foo<Faz> fb1;
}

信用到期,这是 Dietmar Kühl 给我的稍微修改过的版本)

【问题讨论】:

  • Visual Studio 给您带来的实际错误是什么?
  • @James:在问题中添加了错误
  • 也许您应该链接到 Dietmar 建议该方法的原始问题。此外,在他的答案中添加评论并附上此问题的链接可能会引起他的注意。我确信他知道这个问题的答案。
  • 嘿,我做的第一件事是在 connect.microsoft.com 中搜索该错误号并查看您的帖子。 :)
  • @DavidRodriguez-dribeas:比这要复杂一些。他在单独的讨论中展示了这段代码,我在其中遇到了外联定义的语法问题。后来我添加了额外的部分(|| 和它之后的部分),它没有在 VC++ 中编译但在 gcc 上编译(他尝试了除 VC++ 之外的所有编译器,我相信他在 iOS 上)因此这个问题。

标签: c++ templates visual-c++


【解决方案1】:

Visual C++ 2008/2010 有问题。但它可以解决 - 以不止一种方式。

让我们考虑Foo&lt;BasePolicy2&gt; fb的类型。

此声明将模板 Foo 的第二个模板参数默认为首次声明。所以明确它的类型是:

/*1*/ Foo<BasePolicy2,cool::enable_if<
            cool::is_same<BasePolicy, BasePolicy2>::value ||
            cool::is_same<BasePolicy2,BasePolicy2>::value
        >::type
    >

如果您已经对 /*1*/ 归结为:

/*2*/ Foo<BasePolicy2,void>

那么您可以在下面的 The Rendezvous 再次见到我们。

嗯,我们可以看到类型:

/*3/ cool::enable_if<
        cool::is_same<BasePolicy, BasePolicy2>::value ||
        cool::is_same<BasePolicy2,BasePolicy2>::value
    >

解决:

/*4/ cool::enable_if<some_boolean_consant>

接下来,让我们看看template enable_if&lt;&gt; 是如何定义的。在namespace cool 我们有:

/*5/ template <bool, typename = void> struct enable_if {};
/*6/ template <typename T> struct enable_if<true, T> { typedef T type; };

所以/*4*/又默认了template enable_if&lt;&gt;的第二个模板参数,默认的类型是void

好的,那么只要/*6*/ 的第一个 bool 参数具有 值true,它说在这种情况下,enable_if&lt;&gt; 应导出具有第二个模板类型的 typedef type 范围。如果第一个 bool 参数是 false,那么 typedef 将不存在,我们的编译器会出错。

好的,我们知道如果/*4*/ 可以编译,那么some_boolean_consant == true,导出的类型typeenable_if&lt;&gt; 的默认第二个模板参数。这是void

现在已经推断出以下类型:

/*7*/   cool::enable_if<
            cool::is_same<BasePolicy, BasePolicy2>::value ||
            cool::is_same<BasePolicy2,BasePolicy2>::value
        >::type

它是void。所以/*1*/ 归结为/*2*/,这是Foo&lt;BasePolicy2&gt; 的默认类型。

应该是这样,但是如果您对这个结论有疑问,那么只需将其添加到全局范围的程序中并编译:

typedef cool::enable_if<
            cool::is_same<BasePolicy, BasePolicy2>::value ||
            cool::is_same<BasePolicy2,BasePolicy2>::value
        >::type WhatType;
WhatType what_am_i;

编译器会说:

'what_am_i' : illegal use of type 'void'

或类似的词。

会合

/*1/ = /*2/ 的知识让我们对问题所在的 Visual C++ 编译错误有了一些利用:

Error 1 error C2039: '{ctor}' : is not a member of 'Foo' main.cpp 25

错误是抱怨产生它的构造函数实际上不是由它必须属于的类型声明的构造函数,即 Foo&lt;Policy&gt;::Foo() 不是 Foo&lt;Policy&gt; 的构造函数。

现在Foo&lt;Policy&gt; 的定义默认了其初始声明的第二个模板参数,我们知道它必须是void。所以 问题出现了:编译器实际上是否尊重默认的第二个模板参数? - 即它是否承认 Foo&lt;Policy&gt;的模板定义是Foo&lt;Policy,void&gt;的模板定义?

答案是否定的。因为如果我们简单地更改模板及其构造函数的定义以显式指定默认的第二个参数:

template <typename Policy>
struct Foo<Policy,void> {
    Foo();
};

template <typename Policy>
Foo<Policy,void>::Foo() {
}

然后程序用 Visual C++ 编译干净。

Visual C++ 是否可能通过引发此错误来维护自己对标准 C++ 的(值得商榷的)信念?或者它只是坏了? 它只是坏了。因为如果我们在这个更简单但本质上相同的程序上尝试它,它就没有问题:

/* Simple Case */

template<typename X, typename Y = void>
struct A;

template<typename X>
struct A<X> {
    A();
};

template<typename X>
A<X>::A(){};

int main()
{
    A<int> aint;
    return 0;
}

这表明导致消化不良的是满口的模板元编程/*3*/, 正如提问者指出的那样,如果通过删除|| 操作实验性地简化了这一口, 一切都很好(当然,我们的cool:: 逻辑被破坏了)。

解决方法?

我们已经看到了一个。只需在 Foo&lt;Policy&gt; 的定义中显式指定 void 模板参数即可 和Foo&lt;Folicy&gt;::Foo()。如果您只想知道这些,您现在可以离开。

但是那个痒。当我们知道该错误不是时,我们将在/* Simple Case */ 级别应用修复 一般在那个级别。它在编译器的模板元编程工作的默默无闻中,所以 不痒的解决方法至少仅限于namespace cool

以下是关于模板元编程 (TMP) 帮助程序模板的注意规则,我希望它的进展 编译器很快就会让我忘记:不要让 blighters 在飞行中实例化。此类模板仅存在 以促进编译时逻辑。只要编译器可以通过递归定义来执行该逻辑 类型它可能会留在它的舒适区(至少只要它的实例化深度保持不变)。如果 它必须实例化中间类型以促进编译时逻辑,然后是最奇怪的 peccadilloes 容易浮出水面。

如果您对 TMP 帮助程序类进行编码,编译时逻辑只能通过获取值来完成 它们公开的静态或枚举常量,然后你强制编译器一直实例化, 因为只有这样那些静态或枚举常量才能获取值。

举个例子,/*3*/ 类具有神秘的毒性 || 操作。 namespace cool 的 TMP 助手类做他们的 布尔常量的元逻辑 - 不必要。

TMP 辅助类的谨慎方法是定义它们,以便所有逻辑操作都可以模拟、无需实例化、 通过递归类型定义,常量被导出——以免你最终需要它们——只有当所有的编译时间 逻辑成功落地。谨慎地重写namespace cool 的内容可能看起来像这样:

namespace cool
{
    template<bool val>
    struct truth_type
    {
        static const bool value = false;
    };

    template<>
    struct truth_type<true>
    {
        static const bool value = true;
    };

    typedef truth_type<true> true_type;
    typedef truth_type<false> false_type; 

    template<class lhs,class rhs>
    struct or_type
    {
        typedef false_type type;
    };

    template<class lhs>
    struct or_type<lhs,true_type>
    {
        typedef true_type type;
    };

    template<class rhs>
    struct or_type<true_type,rhs>
    {
        typedef true_type type;
    };

    template <typename T, typename = void> struct enable_if {};
    template <typename T> struct enable_if<true_type, T> { typedef T type; };

    template <typename T0, typename T1> struct is_same {
        typedef false_type type;
    };
    template <typename T> struct is_same<T, T> {
        typedef true_type type;
    };
}

模板 Foo 的相应声明如下所示:

template <typename Policy,
typename = typename cool::enable_if<
    typename cool::or_type<
        typename cool::is_same<BasePolicy, Policy>::type,
        typename cool::is_same<BasePolicy2, Policy>::type
    >::type
>::type>
struct Foo;

这样,在整个 shebang 完成之前,我们的 cool:: 都不会被实例化 实例化:我们只处理所有元逻辑的类型。如果这些变化 对程序进行了处理,则不需要发痒的解决方法。 GCC 对此也很满意。

【讨论】:

    【解决方案2】:

    在我看来这与编译器的 bug 有关。

    【讨论】:

      【解决方案3】:

      哦。不应该是:

      template <typename Policy>
      struct Foo {
        Foo();
      };
      
      template <typename Policy>
      Foo<Policy>::Foo() {
      }
      

      (在结构声明中没有&lt;Policy&gt;。)不确定标准是怎么说的,但是在结构名称之后使用模板参数声明模板结构应该声明该模板的部分特化。

      【讨论】:

      • 不,不应该。在 OP 的代码中,您所说的声明是 previously declared 模板Foo部分特化。语法对于部分特化是正确的。您提出的对于模板的“主要”声明是正确的。但在 OP 的代码中,它不是主要声明,而是专门化。
      • 啊,是的,我真傻。我无法一直滚动查看整个代码。 :)
      猜你喜欢
      • 2018-09-28
      • 1970-01-01
      • 1970-01-01
      • 2015-08-01
      • 1970-01-01
      • 2020-07-18
      • 2012-07-12
      • 2023-03-29
      • 1970-01-01
      相关资源
      最近更新 更多