没有 constexpr 的模板非类型参数的类型转换
Type conversion at template non-type argument without constexpr
考虑以下代码:
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
似乎应该是 "usable in constant expression",正如 static_assert
证明的那样。此外,i
可以以相同的方式使用(标记为 (1)
)这一事实,以及 bar()
编译的事实,也让我认为 gcc 是错误的。
UPD:报告了一个针对 gcc 的错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68588
正如@Jarod42 建议的那样,a
应该是 constexpr
。这是因为模板是在编译时推导的,因此非类型参数也必须在编译时可用。 constexpr
承诺它们将在编译时可用。
[expr.const]/(4.1), and I don't see a single applicable bullet point in [expr.const]/2 允许用户定义的转换,这会阻止您的表达式成为常量。事实上,要求如此宽松以至于声明 a
为
A a;
是 still giving a well-formed program,即使 a
没有 constexpr
默认构造函数等,因为转换运算符是 constexpr
并且没有评估任何成员。
如您所见,GCC 是矛盾的,因为它允许 static_assert
条件中的 a
但不允许模板参数。
我会说 Clang 是正确的。
当前 C++ 草案 (n4296) 说:
14.3.2 Template non-type arguments [temp.arg.nontype]
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of
the type of the template-parameter
5.20 §4 说(强调我的):
5.20 Constant expressions [expr.const]
...
(4) A converted constant expression of type T is an expression, implicitly converted to type T, where the converted
expression is a constant expression and the implicit conversion sequence contains only
(4.1) — user-defined conversions, ...
IFAIK in foo<a>();
a 通过 constexpr 用户定义的转换转换为 int,因此 是 转换后的常量表达式。
话虽如此,我们离边缘案例不远了,我的建议是:不要在生产代码中使用这样的结构:-)
考虑以下代码:
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
似乎应该是 "usable in constant expression",正如 static_assert
证明的那样。此外,i
可以以相同的方式使用(标记为 (1)
)这一事实,以及 bar()
编译的事实,也让我认为 gcc 是错误的。
UPD:报告了一个针对 gcc 的错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68588
正如@Jarod42 建议的那样,a
应该是 constexpr
。这是因为模板是在编译时推导的,因此非类型参数也必须在编译时可用。 constexpr
承诺它们将在编译时可用。
[expr.const]/(4.1), and I don't see a single applicable bullet point in [expr.const]/2 允许用户定义的转换,这会阻止您的表达式成为常量。事实上,要求如此宽松以至于声明 a
为
A a;
是 still giving a well-formed program,即使 a
没有 constexpr
默认构造函数等,因为转换运算符是 constexpr
并且没有评估任何成员。
如您所见,GCC 是矛盾的,因为它允许 static_assert
条件中的 a
但不允许模板参数。
我会说 Clang 是正确的。
当前 C++ 草案 (n4296) 说:
14.3.2 Template non-type arguments [temp.arg.nontype]
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter
5.20 §4 说(强调我的):
5.20 Constant expressions [expr.const]
...
(4) A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only
(4.1) — user-defined conversions, ...
IFAIK in foo<a>();
a 通过 constexpr 用户定义的转换转换为 int,因此 是 转换后的常量表达式。
话虽如此,我们离边缘案例不远了,我的建议是:不要在生产代码中使用这样的结构:-)