gcc 模板实例化错误不明确

ambiguous template instantiation error with gcc

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

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

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

聚合类型由基class的列表组成,例如

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

使用带有长度为 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: March 2017 post-Kona working draft/C++17 DIS,并且所有示例程序结果在 GCC 和 Clang 上对于 C++11、C++14 和 C++17 是一致的)

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

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

For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates:

  • (1.1) Each of the two function templates has the same template parameters as the corresponding partial specialization.
  • (1.2) Each function template has a single function parameter whose type is a class template specialization where the template arguments are the corresponding template parameters from the function template for each template argument in the template-argument-list of the simple-template-id of the partial specialization.

因此,为了分析顺序,我们将 class 模板特化重写为函数模板,如上所述:

// 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.func.order]/3 and [temp.func.order]/4 [emphasis mine]:

控制

[temp.func.order]/2

Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

[temp.func.order]/3

To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. [...]

[temp.func.order]/4

Using the transformed function template's function type, perform type deduction against the other template as described in [temp.deduct.partial]. [...]

因此,为上面的两个 f 重载生成转换后的模板,特别是考虑模板模板参数 AggregateType(在两个重载中使用)和这些重载的实例化class 模板 Aggregate 和 classes FooBar,

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

class Foo {};
class Bar {};

分别作为模板模板形参和模板形参的实参,在继续分析原class 模板:

// 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.deduct.partial]/2, [temp.deduct.partial]/10 and [temp.deduct.partial]/11[摘录,强调我的]:

[temp.deduct.partial]/2

Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. [...] The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template.

[temp.deduct.partial]/10

Function template F is at least as specialized as function template G if, for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G. F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.

[temp.deduct.partial]/11

If, after considering the above, function template F is at least as specialized as function template G and vice-versa, and if G has a trailing parameter pack for which F does not have a corresponding parameter, and if F does not have a trailing parameter pack, then F is more specialized than G.

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

无论哪种方式,根据 /10 和 /11,或者单独根据 /10,Specialized 别名

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

明确指代 MyClass 的 "second specialization"(来自 OPs post),特别是重写到上面 F 函数模板的特化,因为这个 class 模板专业化比 "first specialization"(具有附加 Bases2 可变模板参数包的那个)更专业化。