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