请考虑以下代码段:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
clang ++ (主干)编译代码
g ++ (trunk)编译失败,出现以下错误:
src:7:34: error: template argument 1 is invalid
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:25: error: invalid template-id
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:36: error: class template argument deduction failed:
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:36: error: no matching function for call to 'B()'
src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
template <bool> struct B { };
^
src:1:24: note: template argument deduction/substitution failed:
src:7:36: note: couldn't deduce template parameter '<anonymous>'
auto f(T t) -> decltype(B<pred(t)>{})
^
尽管g ++的诊断具有误导性,但我认为这里的问题是t
不是常量表达式。将代码更改为...
decltype(B<pred(T{})>{})
...修复了g ++上的编译错误:live example on godbolt.org
这里的编译器行为正确吗?
答案 0 :(得分:4)
GCC是错误的。没有规则可以以这种方式阻止在常量表达式中使用函数的参数。
但是,您不能在这样的上下文中使用参数的 value ,并且T
可以调用的类型f
的集合非常有限。要了解原因,我们需要考虑在评估表达式pred(t)
时将评估哪些构造:
// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});
呼叫pred(t)
的评估语义如下:
pred
的参数u
复制f
的参数t
pred
的正文,该正文简单地创建一个bool
值true
u
因此,f
仅可用于类型T
,而上述类型仅涉及在常量求值期间有效的构造(有关规则,请参见[expr.const]p2)。要求是:
T
必须是文字类型u
中t
的t
的任何成员执行左值到右值转换(因为它们的值未知),并且不得命名t
实际上,这意味着如果f
是具有默认复制构造函数的空类类型,或者T
是其复制构造函数为{{ 1}},并且不读取其参数的任何成员,或者(奇怪的是)如果T
是constexpr
(尽管clang currently gets the nullptr_t
case wrong)。
答案 1 :(得分:0)
编译器期望该上下文中的参数,因为它需要评估完整(模板重载)函数类型。鉴于pred的实现,任何值都可以在该位置工作。这里它将f参数的模板类型绑定到参数。
g ++编译器似乎在做一个简化的假设,即模板constexpr
函数会以某种方式被任何参数改变,除非它们也是const
,正如你所证明的那样,并且clang同意,不是必然如此。
这一切都归结为函数实现内部有多深,由于非const对返回值的贡献,编译器将函数标记为非const。
然后问题是函数是否被实例化并且需要编译器实际编译代码与执行模板解析,至少使用g ++,它似乎是不同的编译级别。
然后我去了标准,他们恳请编译器编写者完全做出简化假设,模板函数实例化只适用于f<const T>
或f <const T&>
constexpr`函数必须具有:每个参数必须是 LiteralType
因此模板代码应该编译但如果使用非const T进行实例化则会失败。
答案 2 :(得分:-1)
t
不是constexpr值,这意味着pred(t)
也不是constexpr。
您不能在B<pred(t)>
中使用它,因为这需要constexpr。
此版本可正确编译:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}
另一个有效的代码是:
template <typename T>
auto f(T t) -> decltype(pred(t))
{
}
这是因为仅获得类型信息,您不会评估pred(t)
。
B<pred(t)>
需要对pred(t)
进行评估,否则您将获得B<true>
或B<false>
,对于任何常规值您都无法做到这一点。
std::integral_constant<int, 0>{}
可以在Clang情况下工作,可能是因为其值作为类型的一部分内置并且始终相同。如果我们稍微更改代码:
template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
return {};
}
如果std::integral_constant
和t
始终具有相同的值,则Clang和GCC都会对其进行编译。