函数参数的 MSVC 和 constexpr?
MSVC and constexpr for function parameter?
这段代码可以用 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 的错误吗?
是的,这里MSVC是错误的。
代码格式正确似乎违反直觉,因为 v
不是常量表达式,怎么可能用在常量表达式中?
那为什么允许呢?首先,非正式地考虑一下,如果表达式求值为本身不是常量表达式的泛左值或在封闭表达式 ([expr.const]p2.7) 之外开始其生命的变量,则它不是常量表达式。
其次,operator<
是constexpr
。
现在,v < 5
是一个有效的常量表达式。为了理解这一点,让我们看一下表达式的求值。
我们有:
v < 5
打电话给你的 constexpr
operator<
- 两个参数被复制(它们都是文字并且none评估为非constexpr对象)
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);
}
如果您已声明:
template<size_t n>
struct N {
int i;
static constexpr size_t v = n;
};
演示 here.
MSVC、Clang 和 GCC 都会拒绝您的代码。原因是v
被复制到operator<
的第一个参数中。这样的副本是对 v
的求值,而 v
不是常量表达式。
在你的例子中 N
是一个空的 class。我不认为标准规定了这样一个 class 的复制构造函数是否应该访问对象 1 (core language issue 1701) 的内存。因此,编译器显示的行为取决于是否访问空 class 对象的内存。
Clang 和 GCC,在传递参数时不访问空 classes 对象的内存,但 MSVC 会:请参阅此 compiler explorer link
所以我认为所有的编译器都是对的。
1 访问对象的内存表示以复制填充位将涉及 reinterpret_cast
(或等效),这在常量表达式中也是被禁止的。
这里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:
an invocation of a function other than a constexpr constructor for a literal class or a constexpr
function [ Note: Overload resolution (13.3) is applied as usual —end note ];
很好,operator<
是 constexpr 函数,N
的复制构造函数是文字 class.
的 constexpr 构造函数
移动到 operator<
并检查比较 n1.v < n2
如果我们查看 [expr.const]p2.9:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
- a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
- a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers
to a sub-object of such an object, or
- a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not
ended, initialized with a constant expression
我们这里也不错。在最初的示例中,我们指的是可用于常量表达式的模板非类型参数,因此同样的推理也适用于该情况。 <
的两个操作数都是可用的常量表达式。
我们也可以看到 MSVC still treats the simplified case as ill-formed 即使 clang 和 gcc 接受它。
这段代码可以用 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 的错误吗?
是的,这里MSVC是错误的。
代码格式正确似乎违反直觉,因为 v
不是常量表达式,怎么可能用在常量表达式中?
那为什么允许呢?首先,非正式地考虑一下,如果表达式求值为本身不是常量表达式的泛左值或在封闭表达式 ([expr.const]p2.7) 之外开始其生命的变量,则它不是常量表达式。
其次,operator<
是constexpr
。
现在,v < 5
是一个有效的常量表达式。为了理解这一点,让我们看一下表达式的求值。
我们有:
v < 5
打电话给你的constexpr
operator<
- 两个参数被复制(它们都是文字并且none评估为非constexpr对象)
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);
}
如果您已声明:
template<size_t n>
struct N {
int i;
static constexpr size_t v = n;
};
演示 here.
MSVC、Clang 和 GCC 都会拒绝您的代码。原因是v
被复制到operator<
的第一个参数中。这样的副本是对 v
的求值,而 v
不是常量表达式。
在你的例子中 N
是一个空的 class。我不认为标准规定了这样一个 class 的复制构造函数是否应该访问对象 1 (core language issue 1701) 的内存。因此,编译器显示的行为取决于是否访问空 class 对象的内存。
Clang 和 GCC,在传递参数时不访问空 classes 对象的内存,但 MSVC 会:请参阅此 compiler explorer link
所以我认为所有的编译器都是对的。
1 访问对象的内存表示以复制填充位将涉及 reinterpret_cast
(或等效),这在常量表达式中也是被禁止的。
这里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:
an invocation of a function other than a constexpr constructor for a literal class or a constexpr function [ Note: Overload resolution (13.3) is applied as usual —end note ];
很好,operator<
是 constexpr 函数,N
的复制构造函数是文字 class.
移动到 operator<
并检查比较 n1.v < n2
如果我们查看 [expr.const]p2.9:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
- a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
- a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
- a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression
我们这里也不错。在最初的示例中,我们指的是可用于常量表达式的模板非类型参数,因此同样的推理也适用于该情况。 <
的两个操作数都是可用的常量表达式。
我们也可以看到 MSVC still treats the simplified case as ill-formed 即使 clang 和 gcc 接受它。