类模板特化优先级/歧义

时间:2016-11-30 15:23:24

标签: c++ c++11 language-lawyer variadic-templates template-specialization

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

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();
}

在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...>> {

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

  

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

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

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

标准解释(C++14 current draft

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

现在尝试使用它:

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

int main() {
    A<int> a;
    foo(a);
}

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

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

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

1 个答案:

答案 0 :(得分:2)

来自temp.class.order

  

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

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

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

  •   

顺序:

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

取决于以下顺序:

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

来自[temp.func.order]

  

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

     

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

     

使用转换后的函数模板的函数类型,对[temp.func.order]中描述的其他模板执行类型推导。

根据这些段落,对于从任何合成模板Z0和类型T0转换而来的任何函数,可以形成#1,我们可以使用#2进行类型推导。但是,#2使用虚构模板Z2转换为任何类型T2以及任何非空集合Args2的函数都无法从#1推断出来。 #1显然比#2更专业。

在这种情况下,

clang ++是正确的。

实际上,[temp.deduct.partial]this one在g ++和clang中都无法编译(因为含糊不清)。似乎两个编译器都很难使用模板模板参数。 (后者显然是有序的,因为它的顺序与没有函数调用相同)。