【问题标题】:Should the following code compile according to C++ standard?下面的代码是否应该按照 C++ 标准编译?
【发布时间】:2017-04-29 20:06:20
【问题描述】:
#include <type_traits>

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template <typename T>
struct C<first<T, std::enable_if_t<std::is_same<T, int>::value>>>
{
};

int main ()
{
}

不同编译器编译结果:

MSVC:

错误 C2753:“C”:部分特化无法匹配主模板的参数列表

gcc-4.9:

错误:部分专业化'C'不专业从任何模板参数

clang 所有版本:

错误:类模板部分特化没有特化任何模板参数;要定义主模板,请删除模板参数列表

gcc-5+: 编译成功

和辅助我想指出琐碎的专业化,如:

template<typename T>
struct C<T>
{
};

gcc 编译成功失败。所以它似乎发现我原来的例子中的专业化是不平凡的。所以我的问题是 - C++ 标准是否明确禁止这样的模式?

【问题讨论】:

标签: c++ templates gcc language-lawyer sfinae


【解决方案1】:

关键段落是[temp.class.spec]/(8.2),它要求部分特化比主模板更特化。 Clang 实际上抱怨的是参数列表与主模板的相同:最近,issue 2033 已将其从 [temp.class.spec]/(8.3) 中删除(声明该要求是多余的),因此尚未在 Clang 中实现。但是,它显然已在 GCC 中实现,因为它接受您的 sn-p;它甚至是compiles the following,也许出于同样的原因它会编译你的代码(它也只能从版本 5 开始工作):

template <typename T>
void f( C<T> ) {}

template <typename T>
void f( C<first<T, std::enable_if_t<std::is_same<T, int>::value>>> ) {}

即它承认声明是不同的,因此必须对问题1980 进行了一些解决。它没有发现第二个重载更专业(请参阅 Wandbox 链接),但是,这是不一致的,因为它应该根据 (8.2) 中的上述约束来诊断您的代码。

可以说,当前的措辞使您的示例的部分排序按预期工作[temp.deduct.type]/1 提到从类型中推断,

可以在几种不同的上下文中推导出模板参数,但在每种情况下,都会将根据模板参数指定的类型(称为P)与实际类型(称为A)进行比较,然后尝试查找模板参数值 […],这将使 P在替换推导值后(称为推导 A),与 A 兼容。

现在通过[temp.alias]/3,这意味着在偏特化的函数模板是参数模板的偏排序步骤中,替换为is_same 会产生错误(因为公共库实现只使用必须失败),而enable_if 失败。 但是这种语义在一般情况下并不令人满意,因为我们可以构造一个通常成功的条件,因此唯一的合成类型满足它,并且推导都成功方式。

大概,最简单和最强大的解决方案是在部分排序期间忽略丢弃的参数(使您的示例格式错误)。在这种情况下,人们也可以将自己定位于实现的行为(类似于问题1157):

template <typename...> struct C {};

template <typename T>
void f( C<T, int> ) = delete;

template <typename T>
void f( C<T, std::enable_if_t<sizeof(T) == sizeof(int), int>> ) {}

int main() {f<int>({});}

ClangGCC 都将此诊断为调用已删除的函数,即同意第一个重载比另一个重载更专业。 #2 的关键属性似乎是第二个模板参数是依赖的,但 T 仅出现在非推导上下文中(如果我们将 #1 中的 int 更改为 T,则没有任何变化)。所以我们可以使用丢弃的(和依赖的?)模板参数的存在作为平局:这样我们就不必推理合成值的性质,这是现状,并且在你的情况下也得到合理的行为,这将是格式良好的。


@T.C.提到通过 [temp.class.order] 生成的模板当前将被解释为一个多重声明的实体 - 再次,请参阅问题 1980。在这种情况下,这与标准语言没有直接关系,因为措辞从来没有提到这些函数模板是声明的,更不用说在同一个程序中了;它只是指定它们,然后回退到函数模板的过程。

执行此分析所需的深度实现并不完全清楚。问题1157 演示了“正确”确定模板的域是否是另一个域的适当子集所需的详细程度。将部分排序实现得如此复杂既不实际也不合理。但是,脚注部分只是表明该主题不一定未指定,而是有缺陷。

【讨论】:

    【解决方案2】:

    我认为你可以简化你的代码——这与 type_traits 无关。您将获得与以下结果相同的结果:

    template <typename T>
    struct C;
    
    template<typename T>
    using first = T;
    
    template <typename T>
    struct C<first<T>>  // OK only in 5.1
    {
    };
    
    int main ()
    {
    }
    

    签入在线编译器(在 5.1 下编译,但不是在 5.2 或 4.9 下编译,所以这可能是一个错误)-https://godbolt.org/g/iVCbdm

    我认为 int GCC 5 他们围绕模板功能移动,甚至可以创建两个相同类型的专业化。在您尝试使用它之前,它将一直编译。

    template <typename T>
    struct C;
    
    template<typename T1, typename T2>
    using first = T1;
    
    template<typename T1, typename T2>
    using second = T2;
    
    template <typename T>
    struct C<first<T, T>>  // OK on 5.1+
    {
    };
    
    template <typename T>
    struct C<second<T, T>>  // OK on 5.1+
    {
    };
    
    int main ()
    {
       C<first<int, int>> dummy; // error: ambiguous template instantiation for 'struct C<int>'
    }
    

    https://godbolt.org/g/6oNGDP

    这可能与增加对 C++14 变量模板的支持有关。 https://isocpp.org/files/papers/N3651.pdf

    【讨论】:

    • 在 5.1 中您的示例似乎有效,但从 5.2 开始,即使使用一个带有“错误:部分专业化'struct C' 不专门化任何模板参数”的简单模板别名,它也无法编译.所以 gcc 在这方面似乎一直在发展:)
    • 很可能是 N3651 引入到 GCC 5.1 并在 5.2 中部分修复的错误。部分原因是第二个示例将在 5.2 到 6.2 上编译(没有进一步测试)。请注意,我通过添加第二个虚拟模板类型来编辑第二个示例。我不确定它是否真的是一个错误或一个功能。据我了解,它会将 struct C 专门用于 int 两次。如果是函数,这将是一个问题,因为您可以在单独的编译单元中专门化函数并与之链接——您将获得多个定义。在类/结构的情况下,它有点复杂。
    • 无论如何,如果编译器允许您为模板类定义多个专业化,我们如何决定哪一个?我们不能。
    • 仅供参考,我将其报告给 GCC 以获得澄清或修复错误。 gcc.gnu.org/bugzilla/show_bug.cgi?id=78825
    猜你喜欢
    • 2015-12-02
    • 2013-06-20
    • 2015-06-24
    • 2019-10-15
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2012-01-14
    • 2014-02-01
    相关资源
    最近更新 更多