这段代码用clang和gcc编译得很好。
template<size_t n>
struct N {
static constexpr size_t v = n;
};
template<size_t n>
constexpr bool operator<(N<n>, size_t n2) {
return n < n2;
}
template<typename N>
constexpr void foo(N v) {
static_assert(v < 5);
}
int main()
{
foo(N<3>{});
return 0;
}
但是,如果我使用MSVC,我得到的错误是v < 5
不是常量表达式。我能理解为什么MSVC会这么想,但我认为这是错误的,而clang / gcc是对的。这是来自MSVC的错误吗?
如果您已声明:
template<size_t n>
struct N {
int i;
static constexpr size_t v = n;
};
但是,是的zxsw poi。
MSVC,Clang和GCC都会拒绝您的代码。原因是here被复制到v
的第一个参数。这样的副本是对operator<
的评价,而v
不是一个常数表达式。
在你的情况下,v
是一个空类。我不认为标准指定这样的类的复制构造函数是否应该访问对象1(N
)的内存。因此编译器显示的行为取决于是否访问了空类对象的内存。
Clang和GCC在传递参数时不访问空类对象的内存,但是MSVC会这样做:看这个core language issue 1701
所以我认为所有编译器都是正确的。
1访问对象的内存表示以复制填充位将涉及compiler explorer link(或等效的),其在常量表达式中也是禁止的。
是的,MSVC在这里是错误的。
代码结构良好似乎是违反直觉的,因为reinterpret_cast
(不是常量表达式)怎么可能用在常量表达式中呢?
那么为什么允许呢?首先,非正式地考虑,如果表达式计算为glvalue本身不是常量表达式,或者是一个在封闭表达式之外开始生命的变量(v
),则表达式不是常量表达式。
第二,[expr.const]p2.7是operator<
。
现在,会发生的是constexpr
是一个有效的常量表达式。要理解这一点,我们来看看表达式的评估。
我们有:
v < 5
在v < 5
的评估中开始了它的生活并且是文字constexpr
是一个非类型模板参数,因此可用于常量表达式所有这些都没有违反operator<
中的任何一点,因此得到的表达式实际上是一个常量表达式,可用作n2
的参数。
这些类型的表达式称为v < 5
。
这是一个简化的例子:
n
这里MSVC不正确,让我们从代码的简化版本开始:
n < n2
我们先看一下[expr.const]p2,我们是否违反了常量表达式的规则?如果我们看看static_assert
:
为文字类或constexpr函数调用除constexpr构造函数之外的函数[注意:过载分辨率(13.3)按常规应用 - 结束注释];
我们很好,converted constant expressions是constexpr函数,struct Foo {
constexpr operator bool() { return true; }
};
int main() {
Foo f;
static_assert(f);
}
的复制构造函数是文字类的constexpr构造函数。
移动到struct N {
static constexpr size_t v = 0;
};
constexpr
bool operator<(N n1, size_t n2) {
return n1.v < n2;
}
void foo(N v) {
static_assert(v < 5, ""); // C++11 does not allow terse form
}
并检查比较static_assert
,如果我们看看[expr.const]p2.2:
除非适用,否则左值到右值的转换(4.1) - 一个整数或枚举类型的glvalue,它引用具有前面初始化的非易失性const对象,用常量表达式初始化,或者 - 文字类型的glvalue,引用用constexpr定义的非易失性对象,或引用此类对象的子对象,或者 - 一个文字类型的glvalue,它引用一个生命周期尚未结束的非易失性临时对象,用一个常量表达式初始化
我们在这里也很好。在原始示例中,我们指的是一个模板非类型参数,它可以在常量表达式中使用,因此同样的推理也适用于该情况。 operator<
的两个操作数都是可用的常量表达式。
我们也可以看到N
即使clang和gcc接受它。