MSVC和constexpr函数参数?

时间:2018-09-15 17:10:20

标签: c++ c++11 language-lawyer constexpr

此代码可以使用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的错误吗?

3 个答案:

答案 0 :(得分:5)

是的,MSVC在这里是错误的。

代码格式正确似乎是违反直觉的,因为不是常量表达式的v怎么可能在常量表达式中使用?

那为什么允许它?首先,请非正式地考虑,如果表达式的计算结果是glvalue,而该值本身不是常量表达式,也不是封闭表达式([expr.const]p2.7之外的变量),则它不是常量表达式。

第二,operator<constexpr

现在,发生的情况是v < 5是有效的常量表达式。要了解这一点,让我们对表达式进行评估。

我们有:

  1. v < 5呼叫您的constexpr operator<
  2. 将复制这两个参数(它们都是文字,并且都不计算为非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);
}

答案 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(或等效值),在常量表达式中也禁止使用。