在没有constexpr的模板非类型参数处进行类型转换

Type conversion at template non-type argument without constexpr

本文关键字:类型参数 类型转换 constexpr      更新时间:2023-10-16

考虑以下代码:

struct A {
    constexpr operator int() { return 42; }
};
template <int>
void foo() {}
void bar(A a) {
    foo<a>();
}
int main() {
    foo<A{}>();
    const int i = 42;
    foo<i>();  // (1)
    A a{};
    static_assert(i == a, "");
    bar(a);
    foo<a>();  // error here
}

带有c++14的Clang 3.7接受这一点,而带有c++14的gcc 5.2.0则不接受,生成以下消息:

/tmp/gcc-explorer-compiler1151027-68-1f801jf/example.cpp: In function 'int main()':
26 : error: the value of 'a' is not usable in a constant expression
foo<a>();
^
23 : note: 'a' was not declared 'constexpr'
A a{};
^
Compilation failed

按照gcc的建议将a更改为constexpr可以修复gcc编译错误,但如果没有constexpr,哪个编译器是对的?

对我来说,似乎a应该是"可用于常量表达式",就像static_assert一样。此外,i可以以相同的方式使用(标记为(1)),以及bar()编译的事实,也让我认为gcc是错误的。

UPD:报告了一个针对gcc的错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68588

[expr.const]/(4.1)允许用户定义的转换,在[expr.const]/2中我看不到任何一个适用的要点会阻止您的表达式成为常量。事实上,这些要求非常宽松,以至于将a声明为

A a;

即使a没有constexpr默认构造函数等,它仍然给出了一个格式良好的程序,因为转换运算符是constexpr,并且没有评估任何成员。

正如您自己所看到的,GCC是矛盾的,因为它允许static_assert条件中的a,但不允许模板参数。

我认为Clang是正确的。

当前C++(n4296)的草案说:

14.3.2模板非类型参数[temp.arg.notype]

非类型模板参数的模板参数应为的转换常量表达式(5.20)模板参数的类型

5.20§4说(强调我的):

5.20常量表达式[expr.const]

(4) T类型的转换常量表达式是一个隐式转换为T类型的表达式,其中表达式是一个常量表达式,并且隐式转换序列仅包含

(4.1)--用户定义的转换。。。

foo<a>();a中的IFAIK通过constexpr用户定义转换转换为int,因此转换后的常量表达式。

话虽如此,我们离边缘案例不远了,我的建议是:不要在生产代码中使用这样的构造:-)

As@Jarod42建议a应为constexpr。这是因为模板是在编译时推导的,因此非类型参数也必须在编译时可用。constexpr承诺它们将在编译时可用。