c++ - 类模板特化优先级/歧义

标签 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 认为第一个特化“更特化”(通过部分排序:请参阅下一节),因为它编译良好并打印:

I'm more specialized than the variadic spec, hehe!

一个 live demo 可以在 Coliru 上找到。我也尝试使用 gcc 的 HEAD 版本,得到了同样的错误。

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


标准解释(C++14 current draft)

根据 C++14 标准草案的 §14.5.5.1 和 $14.5.5.2 部分,会触发部分排序以确定应选择哪个特化:

(1.2) — If more than one matching specialization is found, the partial order rules (14.5.5.2) are used to determine whether one of the specializations is more specialized than the others. If none of the specializations is more specialized than all of the other matching specializations, then the use of the class template is ambiguous and the program is ill-formed.

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

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 (14.5.6.2):

(1.1) — the first function template has the same template parameters as the first partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the first partial specialization, and

(1.2) — the second function template has the same template parameters as the second partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the second partial specialization.

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

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 的错误跟踪器,但找不到在这个问题中观察到的函数模板重载行为的票证。

最佳答案

来自 temp.class.order :

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 ([temp.func.order]):

  • Each of the two function templates has the same template parameters as the corresponding partial specialization.

  • 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.

顺序:

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] :

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.

To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs ([temp.variadic]) 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.

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

通过这些段落,对于从任何合成模板 Z0 和类型 T0 转换而来的任何函数,它可以形成 #1,我们可以使用 #2 进行类型推导。但是从 #2 转换而来的函数,带有任何类型 T2 和任何非空 Args2 的虚构模板 Z2不能从 #1 推导出来。 #1 显然比 #2 更专业。

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


其实,this onethis one在 g++ 和 clang 中都无法编译(因为模棱两可)。似乎两个编译器都很难使用模板模板参数。 (后一个是明确排序的,因为它的顺序与没有函数调用的顺序相同)。

关于c++ - 类模板特化优先级/歧义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40892048/

相关文章:

c++ - 将指针/引用转换为固定数组大小是否合法

c++ - 将右值引用传递给 boost::in_place 函数

c++ - 为什么 clang 不允许通过实例访问嵌套的枚举类?

c++ - 将程序参数复制到以空格分隔的 std::string

c++ - 3个顶点之间的角度

c++ - 可以在 gdb 中调用内联函数和/或使用 GCC 发出它们吗?

c++ - C++ 中的正则表达式编译错误

c++ - 为什么我不能增加简单 constexpr 函数的参数?

c++ - 为 I/O 以外的事物重载移位运算符是否是一个好的设计?

c++ - atomic_thread_fence(memory_order_release) 与使用 memory_order_acq_rel 有区别吗?