当前位置: 首页 > 知识库问答 >
问题:

gcc模板实例化不明确错误

胡新
2023-03-14

对于下面的代码,我在gcc中得到了一个模糊的模板实例化错误。但是,使用Clang或Visual Studio,代码可以很好地编译。可以在此处找到代码的完整工作示例:http://coliru.stacked-crooked.com/a/60ef9d73ce95e6f9

我有一个类模板,它是从一个聚合类型构建的

template<template<typename...> typename AggregateType, typename ...>
struct MyClass;

聚合类型由一系列基类组成,例如

template<typename ... Bases>
struct Aggregate : Bases...
{ };

我定义了MyClass的两个专业。第一个专业化是常见情况,并读取

// specialization for two argument list for the
// aggregate type
template<template<typename...> typename AggregateType,
      typename Base,
      typename ... Bases1,
      typename ... Bases2>
struct MyClass<
      AggregateType,
      AggregateType<Bases1...>,
      AggregateType<Base, Bases2...>>
{
      void func()
      {
       std::cout << "not specialized\n";
      }
};

第二个特化处理了第二个基列表只有 1 个参数的情况

// specialization for the second argument list with length 1
template<template<typename...> typename AggregateType,
     typename Base,
     typename ... Bases1>
struct MyClass<
    AggregateType,
    AggregateType<Bases1...>,
    AggregateType<Base>>
{
  void func()
  {
    std::cout << "specialized\n";
  }
};

使用MyClass和长度为1的第二个参数列表,我希望编译器选择MyClass的第二个专门化,因为它是更专门化的模板

class Foo {};
class Bar {};

int main()
{
  // this should give the not specialized class
  using NotSpecialized = MyClass<Aggregate, Aggregate<Foo, Bar>, Aggregate<Foo, Bar>>;
  NotSpecialized ns;
  ns.func();

  // this should give the specialized class
  using Specialized = MyClass<Aggregate, Aggregate<Foo, Bar>, Aggregate<Foo>>;
  Specialized s;
  s.func();
}

虽然代码在Clang中运行良好,但gcc给出了一个模糊的模板实例化错误。我如何绕过这个错误并仍然使用gcc?如果我删除Aggregate Type模板参数,代码也适用于gcc,请参阅http://coliru.stacked-crooked.com/a/c1f6edd5fab7df4d

共有1个答案

李建中
2023-03-14

(以下所有ISO标准参考均参考N4659:2017年3月Kona后工作草案/C 17 DIS,且所有示例程序结果与C 11、C 14和C 17的GCC和Clang一致)

我相信GCC在这里是错误的,但我还没有找到相应的(开放的)GCC错误报告。

[temp.class.order]/1 涵盖了类模板专用化的部分排序 [强调我的]:

对于两个类模板部分特化,如果给定以下对两个函数模板的重写,则第一个函数模板比第二个函数模板更专用,则根据函数模板的排序规则,第一个函数模板比第二个更专用:

  • (1.1)两个函数模板中的每一个都具有与相应的部分专门化相同的模板参数
  • (1.2)每个函数模板都有一个函数参数,其类型为类模板专门化,其中模板参数是部分专门化的简单模板id的模板参数列表中每个模板参数的函数模板中的对应模板参数

因此,为了分析排序,我们将类模板专门化重写为函数模板,如上所述:

// G)
template<template<typename...> typename AggregateType,
         typename Base,
         typename... Bases1,
         typename... Bases2>
void f(MyClass<AggregateType,
               AggregateType<Bases1...>,
               AggregateType<Base, Bases2...>>);

// F)
template<template<typename...> typename AggregateType,
         typename Base,
         typename... Bases1>
void f(MyClass<AggregateType, AggregateType<Bases1...>, AggregateType<Base>>);

F的G和F重载的偏序由[temp.func.order]/2、[temp.funct.order]/3和[temp.funct.order]/4[emphasis mine]控制:

分排序通过依次转换每个模板(请参阅下一段)并使用函数类型执行模板参数推导来选择两个函数模板中哪一个比另一个更专业。扣除过程确定其中一个模板是否比另一个更专业。如果是这样,则更专业的模板是部分订购过程选择的模板。

为了生成转换后的模板,对于每个类型、非类型或模板模板参数(包括其模板参数包),分别合成一个唯一的类型、值或类模板,并将其替换为模板函数类型中该参数的每次出现。[...]

使用转换后的函数模板的函数类型,对另一个模板执行类型推导,如[temp.deduct.partial]中所述。[...]

因此,为了为上面的两个f重载生成转换后的模板,特别考虑模板参数Aggregate Type(在两个重载中使用)以及这些重载的实例化与特定的类模板Aggregate和类FooBar

template<typename ... Bases>
struct Aggregate : Bases...
{ };

class Foo {};
class Bar {};

分别用作模板模板参数和模板参数的参数,在继续分析原始类模板的部分排序时,我们可以在不失一般性的情况下将以下(部分)转换的函数模板视为参数模板:

// G-transformed (argument template>
template<typename... Bases2>
void f(MyClass<Aggregate, Aggregate<Foo, Bar>, Aggregate<Foo, Bases2...>>);

// F-transformed (argument template>
void f(MyClass<Aggregate, Aggregate<Foo, Bar>, Aggregate<Foo>>);

从[临时扣除.部分]/2,[临时扣除.部分]/10和[临时扣除.部分]/11[摘录,强调我的]:

两组类型用于确定偏序。对于所涉及的每个模板,都有原始函数类型和转换后的函数类型。[...]演绎过程使用转换后的类型作为实参模板,使用另一个模板的原始类型作为参数模板。

如果对于用于确定排序的每对类型,F中的类型至少与G中的类型一样专业,则函数模板F至少与函数模板G一样专业。如果F至少与G一样专业,并且G至少与F一样专业,则F比G更专业。

如果,在考虑了上述内容之后,函数模板F至少与函数模板G一样专门化,反之亦然,并且如果G具有尾部参数包,而F没有对应的参数,并且如果F没有尾部参数包,则F比G更专门化。

因此,F 至少与 G (/10) 一样专业化,而且由于 G 中存在(附加的)尾随参数包 Bases2,而 F 比 G (/11) 中更专业。甚至可以直接应用[temp.credit.partial]/10的第二部分来论证F比G作为聚合更专业

无论哪种方式,无论是根据 /10 和 /11,还是单独根据 /10,专用别名

using Specialized = MyClass<Aggregate, Aggregate<Foo, Bar>, Aggregate<Foo>>;

不含糊地指的是MyClass的“第二专门化”(来自OPs post),特别是重新写入上述F函数模板的专门化,因为该类模板专门化比“第一专门化”(具有附加Bases2variadic模板参数包的专门化)更专业。

 类似资料:
  • 我试图移植一些为GCC(8.2)编写的代码以供Clang编译: GCC 8.2(和12.1)很好地编译了代码。然而,Clang 11.0.0(和14.0.0)抱怨说,从fe到feh的调用在无效feh之间不明确(Fn https://godbolt.org/z/5E9M6a5c6 哪个编译器是正确的? 如何编写这段代码以便两个编译器都接受它? 这两个和折叠表达式都可以在C 17中使用,但这是许多项目

  • [dcl.spec.auto]/14国[强调我的]: 显式实例化声明不会导致使用占位符类型声明的实体的实例化,但也不会阻止根据需要对该实体进行实例化以确定其类型。[示例: -结束示例] 和[temp.explicat]/11声明[强调我的]: 现在,考虑我们是否在类模板中的friend声明中定义了friend函数: 如果在同一翻译单元中实例化了的多个专门化,则将违反[basic.def.odr]/

  • 根据我的尝试和错误,答案似乎是否定的,这是一个简单的 将为班上的所有成员工作。然而,我读过的代码建议不是这样,如果能给出具体的答案,我将不胜感激。

  • 例1 如前所述,上面的编译和链接没有问题。然而,在实际应用程序中,我有许多函数模板和许多枚举成员。因此,我试图通过将成员分组到一个新类中来使我的生活变得更轻松,这个类本身依赖于模板参数,并为每个枚举值显式地实例化该类。 例2 编辑:用户2B-T通过https://www.onlinegdb.com/hygr7w0fv_提供了这些示例,供人们进行实验。

  • 我正在从事一个C语言的项目,当我显式实例化模板类时,很难理解模板类的哪些成员被显式实例化。我编写了以下文件,然后使用Visual C 2008 Express Edition的发布配置编译该文件,然后将其放入反汇编程序。 忽略这个文件目前并不真正需要模板,这可以很好地编译。我将exe放入反汇编程序,它告诉我该测试 这导致测试

  • 在SO上回答另一个问题时,我遇到了一个有点可疑的gcc编译器错误。令人不快的片段是 谁的最后一行给出了著名的警告 好友声明'