gcc出现不明确的模板实例化错误

ambiguous template instantiation error with gcc

本文关键字:实例化 错误 不明确 gcc      更新时间:2023-10-16

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

我有一个从aggegate类型构建的类模板

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

当第二个基本列表只有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 << "specializedn";
}
};

使用第二个参数列表长度为1的MyClass,我希望编译器选择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?如果我删除AggregateType模板参数,代码也适用于gcc,请参阅http://coliru.stacked-crooked.com/a/c1f6edd5fab7df4d

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

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

[temp.class.order]/1涵盖类模板专业化的部分排序[emphasismine]:

对于两个类模板的部分专业化,第一个是更多专用于第二个if,给定下面的重写为两个函数模板,第一个函数模板更专业根据函数的排序规则,比第二个模板:

  • (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.foc.order]/3和[temp.fonc.order][emphasismine]控制:

[温度函数顺序]/2

偏序选择两个函数模板中哪一个更大通过依次转换每个模板(请参阅下一段)并执行模板参数推导,使其比其他更专业使用函数类型。扣除过程决定其中一个模板比另一个更专业。如果是更专业的模板是由偏序选择的模板过程

[温度函数顺序]/3

为每个类型、非类型或模板模板参数(包括模板参数包其中)分别合成一个唯一的类型、值或类模板,并将其替换为该参数的每次出现在模板的函数类型中[…]

[温度函数顺序]/4

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

因此,为了生成上述两个f重载的转换模板,特别考虑模板模板参数AggregateType(如在两个重载中使用的)以及这些重载与特定类模板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>>);

从[temp.dexer.partial]/2、[temp.desex.partial]/10和[temp.delete.partial]/11[摘录,emphasismine]:

[临时扣除部分]/2

两组类型用于确定偏序。对于每个在涉及的模板中,有原始函数类型和转换的函数类型。[…]推导过程使用转换类型作为参数模板和的原始类型另一个模板作为参数模板

[临时扣除部分]/10

函数模板F至少与函数模板G一样专业化如果对于用于确定排序的每对类型,来自F的类型至少与来自G的类型一样专业化。如果F至少和G一样专业,而G至少没有F那么专业,则F比G更专业。

【临时扣除部分】/11

如果,在考虑以上内容后,函数模板F至少与函数模板G一样专业,反之亦然,并且如果G具有尾部参数包,而F没有相应的参数;并且如果F<strong]没有尾部参数包>,则F比G更专业。

由此得出,F至少与G(/10)一样专业,而且由于(附加的)尾部参数包Bases2存在于G中而不存在于F(/11)中,因此F比G更专业。甚至可以直接应用[temp.dexer.partial]/10的第二部分来论证F比G更专业,因为Aggregate<Foo>Aggregate<Foo, Bases2...>>更专业。

无论哪种方式,无论是按照/10和/11,还是单独按照/10,Specialized别名

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

无歧义地指MyClass的"第二个专门化"(来自OP帖子),特别是重新写入上面F函数模板的专门化,因为这个类模板专门化比"第一个专门化(带有额外Bases2可变模板参数包的那个)更专门化。