函数参数的 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 是一个有效的常量表达式。为了理解这一点,让我们看一下表达式的求值。

我们有:

  1. v < 5 打电话给你的 constexpr operator<
  2. 两个参数被复制(它们都是文字并且none评估为非constexpr对象)
  3. n2v < 5 的计算中开始它的生命并且是一个文字
  4. n 是非类型模板参数,因此可用于常量表达式
  5. 最后,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 接受它。