类模板特化优先级/歧义

Rer*_*ito 16 c++ template-specialization language-lawyer variadic-templates c++11

在尝试实现依赖于可变参数模板的一些事情时,我偶然发现了一些我无法解释的事情.我将问题归结为以下代码片段:

template <typename ... Args>
struct A {};

template <template <typename...> class Z, typename T>
struct test;

template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
    static void foo() {
        std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
    }
};

template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
    static void foo() {
        std::cout << "I'm variadic!" << std::endl;
    }
};

int main() {
    test<A, A<int>>::foo();
}
Run Code Online (Sandbox Code Playgroud)

在gcc下,它会产生错误,因为它在尝试实例化时认为两个特化都是同样专业的test<A, A<int>>:

main.cpp: In function 'int main()':

main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'

         test<A, A<int>>::foo();

                        ^~

main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]

     struct test<Z, Z<T>> {

            ^~~~~~~~~~~~~

main.cpp:18:12: note:                 template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]

     struct test<Z, Z<T, Args...>> {
Run Code Online (Sandbox Code Playgroud)

然而,clang认为第一个专业化"更专业化"(通过部分排序:参见下一节),因为它编译精细并打印:

我比变量规格更专业,呵呵!

A live demo可以在Coliru找到.我也试过使用gcc的HEAD版本并得到了同样的错误.

我的问题是:由于这两个着名的编译器行为不同,哪一个是正确的,这段代码是正确的C++?


标准口译(C++ 14当前草案)

从C++ 14标准草案的§14.5.5.1和$ 14.5.5.2节中,触发了部分排序以确定应选择哪种特化:

(1.2) - 如果找到多个匹配专业化,则使用偏序规则(14.5.5.2)来确定其中一个专业化是否比其他专业化更专业化.如果没有一个专门化比所有其他匹配专业化更专业化,那么类模板的使用是不明确的,并且程序是不正确的.

现在根据§14.5.5.2,类模板特化通过以下过程转换为函数模板:

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

(1.1) - 第一个函数模板具有与第一个部分特化相同的模板参数,并且具有单个函数参数,其类型是具有第一个部分特化的模板参数的类模板特化,并且

(1.2) - 第二个函数模板具有与第二个部分特化相同的模板参数,并且具有单个函数参数,其类型是具有第二部分特化的模板参数的类模板特化.

因此,我尝试使用上面描述的转换应该生成的函数模板重载来重现该问题:

template <typename T>
void foo(T const&) {
    std::cout << "Generic template\n";
}

template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
    std::cout << "Z<T>: most specialized overload for foo\n";
}

template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
    std::cout << "Z<T, Args...>: variadic overload\n";
}
Run Code Online (Sandbox Code Playgroud)

现在尝试使用它像这样:

template <typename ... Args>
struct A {};

int main() {
    A<int> a;
    foo(a);
}
Run Code Online (Sandbox Code Playgroud)

在clang和gcc中产生编译错误[模糊调用] : live demo. 我希望clang至少会有一个与类模板情况一致的行为.

然后,这是我对标准的解释(我似乎与@Danh分享),所以在这一点上我们需要来证实这一点.

注意:我浏览了一下LLVM的错误跟踪器,但在此问题中找不到功能模板重载时观察到的行为的票证.

Dan*_*anh 2

来自temp.class.order

对于两个类模板部分特化,第一个比第二个更专业如果,给定以下对两个函数模板的重写,根据函数模板的排序规则,第一个函数模板比​​第二个更专业( )[temp.func.order]

  • 两个函数模板中的每一个都具有与相应的部分特化相同的模板参数。

  • 每个函数模板都有一个函数参数,其类型是类模板特化,其中模板参数是部分特化的 simple-template-id 的 template-argument-list 中每个模板参数的函数模板中对应的模板参数。

的顺序:

template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
    static void foo() {
        std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
    }
};

template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
    static void foo() {
        std::cout << "I'm variadic!" << std::endl;
    }
};
Run Code Online (Sandbox Code Playgroud)

取决于以下顺序:

template <template <typename...> class Z, typename T>
void bar(test<Z, Z<T>>); // #1
template <template <typename...> class Z, typename T, typename ... Args>
void bar(test<Z, Z<T, Args...>>); // #2
Run Code Online (Sandbox Code Playgroud)

[temp.func.order]

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

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

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

通过这些段落,对于从任何合成模板Z0和类型转换而来的任何函数T0,可以形成#1,我们可以用 进行类型推导#2。但是用任何类型和任何非空集合的#2虚拟模板转换而来的函数都不能从 中推导出来。显然比Z2T2Args2#1#1#2

clang++ 在这种情况下是正确的。


实际上,这个这个在 g++ 和 clang 中都无法编译(因为不明确)。似乎两个编译器都很难处理模板模板参数。(后一个是明确排序的,因为它的顺序与没有函数调用的顺序相同)。