【问题标题】:Specialization based on array size validity基于数组大小有效性的专业化
【发布时间】:2020-03-17 03:25:06
【问题描述】:

尝试根据数组大小的有效性进行专门化:

// base template
template<int p, typename T = void>
struct absolute {
    operator int () const { return 0; }
};

// positive case template
template<int p>
struct absolute<p, typename std::void_t<int[p]>> {
    operator int () const { return p; }
};

// negative case template
template<int p>
struct absolute<p, typename std::void_t<int[-p]>> {
    operator int () const { return -p; }
};


int main() {
    std::cout << absolute<5>() << std::endl;
    std::cout << absolute<-5>() << std::endl;
    std::cout << absolute<0>() << std::endl;
}

问题 #1:

以上代码works nicely with gcc but fails to compile with clang

Clang 生成错误:redefinition of template struct 'absolute'

谁是对的?


问题 #2:

Both with gcc and with clang(如果我们移除负面专业化以将 clang 带回游戏),不清楚为什么 absolute&lt;0&gt;() 选择基本模板。 There is nothing wrongint[0] 以及 std::void_t&lt;int[0]&gt; 似乎更专业:

// base template
template<int p, typename T = void>
struct absolute {
    operator int () const { return -1; }
};

// positive case template
template<int p>
struct absolute<p, typename std::void_t<int[p]>> {
    operator int () const { return p; }
};

int main() {
    std::cout << absolute<5>() << std::endl; // 5
    std::cout << absolute<0>() << std::endl; // -1, why not 0?
}

并且...如果基本模板只是声明而没有实现,则为:

// base template
template<int p, typename T = void>
struct absolute;

Both gcc and clang would fail to compile,抱怨 不完整类型的无效使用absolute&lt;0&gt;()。尽管它似乎适合特殊情况。

这是为什么呢?

【问题讨论】:

标签: c++ template-specialization


【解决方案1】:

关于Clang的重定义错误,见this question

最初的别名模板(如std::void_t)的模板ID 将简单地替换为它们的别名类型,而不检查替换失败的参数。这已更改为 CWG issue 1558。这仅将标准更改为要求模板参数中的替换失败,但并未阐明在替换别名后等效的两个模板是否应被视为等效。 Clang 认为它们是等价的,但 GCC 不是。这是开放的CWG issue 1980


-pedantic-errors GCC 已经报告了一个硬错误

std::cout << absolute<5>() << std::endl;

在专业中

template<int p>
struct absolute<p, typename std::void_t<int[-p]>>

因为据说数组大小不是一个常量表达式。数组的大小必须是std::size_t 类型的转换常量表达式。转换后的常量表达式只能使用非缩小转换。所以确实-pp = 5 转换为std::size_t 不是一个常量表达式,使得int[-p] 类型不正确,但我认为这应该导致替换失败,而不是硬错误。 C++17 标准(草案 N4659)的[temp.deduct/8] 说:

如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是一种格式错误的类型或表达式,如果使用替换参数编写,则需要进行诊断。

这适用于这里。引用后给出的非规范示例甚至包括负数组大小作为替换失败的示例。

特别奇怪的是,对于absolute&lt;-5&gt;() GCC 确实报告了

template<int p>
struct absolute<p, typename std::void_t<int[p]>>

专业化,其中int[p] 的计算结果为int[-5],它也没有转换后的常量表达式大小。


absolute&lt;0&gt;() 选择主模板,因为数组大小必须大于零,这使得部分特化都不可行。零大小数组是一种语言扩展,可以在 GCC 和 Clang 中使用 -pedantic-errors 禁用。

【讨论】:

  • 实现我们自己的 void_t 版本适用于 clang 和 gcc:godbolt.org/z/-2C8mJ 所以它似乎与 CWG 问题 1558 相关......还是它也处理 CWG 问题 1980?
  • @AmirKirsh 根据您的定义,您还可以绕过 CWG 问题 1980。它们都与如何处理解析为非依赖类型的别名模板有关。您的定义解析为 make_void&lt;Ts...&gt;::type,这是一个依赖类型。
  • @AmirKirsh 我没有意识到 GCC 只会抱怨一个专业化大小不是一个常量表达式。我已经更新了答案以反映这一点,这似乎让我更加清楚 GCC 不一致。
猜你喜欢
  • 1970-01-01
  • 2019-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-22
  • 1970-01-01
  • 2021-02-27
相关资源
最近更新 更多