virtual and constexpr before up to C++17

huangapple go评论73阅读模式
英文:

virtual and constexpr before up to C++17

问题

I understand your request to translate the code part only. Here is the translated code:

我对混合使用 ```virtual```  ```constexpr``` 的成员函数的内容感到困惑。
根据:

- [constexpr and virtual](https://stackoverflow.com/questions/47278502/constexpr-and-virtual)
- [Can virtual functions be constexpr?](https://stackoverflow.com/questions/34828161/can-virtual-functions-be-constexpr)
- [constexpr in cppreference](https://en.cppreference.com/w/cpp/language/constexpr)

直到包括 C++17 在内的版本,应该不可能混合使用 ```constexpr```  ```virtual```(标准在上述链接中引用)

然而,我设计了这个简单的例子:
```c++
#include <cstddef>

struct SizedObject {
    virtual size_t GetSize() const = 0;
};

struct DynSizedObject : public SizedObject {
    size_t s;
    size_t GetSize() const override final { return s; }
};

struct StatSizedObject : public SizedObject {
    const size_t s;
    constexpr size_t GetSize() const override final { return s; }
    constexpr explicit StatSizedObject(const size_t i) : s(i) {}
};

int main(int argc, char** argv) {
    constexpr StatSizedObject SS(42);
    DynSizedObject DS;
    DS.s = argc + 2;
    SizedObject const * p;
    if (argc > 3) {
        p = &SS;
    } else {
        p = &DS;
    }

    return p->GetSize();
}

我正在设计一个具有可以通过 GetSize 成员函数检索的 size 属性的对象层次结构,我将其设置为 virtual。现在,其中一个具体类可以使用编译时大小实例化,并且我使其 GetSize 覆盖为 constexpr。然后,我的示例代码欺骗编译器,使其在编译时不知道将调用 GetSize 的对象。在 Live 中,我惊讶地看到 gcc 接受了这段代码。clang 拒绝了它,似乎符合标准,而 msvc 也像 clang 一样,但还声称 constexpr 版本 不能产生常量表达式,这对我来说似乎是不正确的。

因此,我的理解是所有编译器都应该像 clang 一样行为(但并非如此):这是正确的吗?在这种情况下,gcc 是否存在一个“错误”,它接受了这个构造?附带问题:为什么 msvc 会发出 不能产生常量表达式 错误?

除了学术兴趣之外,我的目的是想知道是否可以在与动态多态性相关的情况下使用一些编译时优化技巧(从 C++20 开始,我知道 一些设计 是可能的)。


Please note that the translation provided is a direct translation of the code. If you have any further questions or need clarifications, please feel free to ask.

<details>
<summary>英文:</summary>

I&#39;m confused about what I read about mixing ```virtual``` and ```constexpr``` for member functions.
According to:

 - [constexpr and virtual](https://stackoverflow.com/questions/47278502/constexpr-and-virtual)
 - [Can virtual functions be constexpr?](https://stackoverflow.com/questions/34828161/can-virtual-functions-be-constexpr)
 - [constexpr in cppreference](https://en.cppreference.com/w/cpp/language/constexpr)

up to C++17 included, it should not be possible to mix ```constexpr``` and ```virtual``` (standard is quoted in above links)

Yet I designed this straightfoward example:
```c++
#include &lt;cstddef&gt;

struct SizedObject {
    virtual size_t GetSize() const = 0;
};

struct DynSizedObject : public SizedObject {
    size_t s;
    size_t GetSize() const override final { return s; }
};

struct StatSizedObject : public SizedObject {
    const size_t s;
    constexpr size_t GetSize() const override final { return s; }
    constexpr explicit StatSizedObject(const size_t i) : s(i) {}
};

int main(int argc, char** argv) {
    constexpr StatSizedObject SS(42);
    DynSizedObject DS;
    DS.s = argc + 2;
    SizedObject const * p;
    if (argc &gt; 3) {
        p = &amp;SS;
    } else {
        p = &amp;DS;
    }

    return p-&gt;GetSize();
}

I'm designing a hierarchy of objects with a size property that can be retrieved through the GetSize member function, which I make virtual. Now one of the concrete class can be instantiated with a compile-time size, and I make its GetSize override constexpr. Then my toy example trick the compiler in not knowing, at compile-time, which object will call GetSize. In the Live I'm surprised to see that gcc accept the code. clang reject it, seemingly consistant with the standard, while msvc do as clang but also claim that the constexpr version cannot result in a constant expression, which seems incorrect to me.

Thus my understanding would be that all compiler should behave like clang (but don't): is it correct? In this case, is it a gcc "bug" that it is accepting this construct? subsidiary question: why does msvc issue the cannot result in a constant expression error?

Beyond an academic interest, my purpose is to know if I may use some compile-time optimisation technics in relation with dynamic polymorphism (up to C++17, I know that, from C++20, some designs are possible).

答案1

得分: 5

C++17

在C++17中,上述代码将是不合法的。你发现的是GCC中的一个编译器错误。标准对此非常明确:

constexpr函数的定义必须满足以下要求:

  • [dcl.constexpr] p3

如果在类Base和类Derived中直接或间接地派生自Base中声明了名为vf、参数类型列表、cv限定符和引用限定符(或缺乏同样限定符)的虚成员函数,则也声明了一个名为vf、参数类型列表、cv限定符和引用限定符(或缺乏同样限定符)的成员函数Derived::vf,那么Derived::vf也是虚的(无论是否声明为虚),并且它会覆盖Base::vf。

  • [class.virtual] p2

从这两个部分我们可以得出结论,constexpr virtual 函数是不允许的,覆盖虚函数的任何函数也不能是 constexpr,因为它隐式地是虚的。

GCC诊断

有趣的是,我们对于GetSize声明为virtual constexpr时没有得到任何诊断,尽管它根据标准是一个虚函数。

// 由于它覆盖了SizedObject::GetSize,所以隐式地是虚的
constexpr size_t GetSize() const override final { return s; }

如果我们多余地标记函数为 virtual,我们会得到诊断:

constexpr virtual size_t GetSize() const override final { return s; }
<source>:14:15: warning: member 'GetSize' can be declared both 'virtual' and > 'constexpr' only in '-std=c++20' or '-std=gnu++20' [-Wc++20-extensions]
   14 |     constexpr virtual size_t GetSize() const override final { return s; }
      |     ~~~~~~~~~ ^~~~~~~

MSVC诊断

至于:

次要问题:为什么MSVC会发出"无法得到常量表达式结果"的错误?

诚然,这不是一个非常直观的错误消息。这只是MSVC对函数由于其签名而无法是constexpr时的输出。当将constexpr函数的返回类型设为std::vector<int>时,你会得到相同的消息。

总的来说,constexpr意味着对于某些参数,函数必须能够得出常量表达式。如果函数签名使这成为不可能,那么指定constexpr是错误的。

C++20

C++20解除了对constexpr virtual函数的限制,因此上述代码将是合法的。

英文:

C++17

In C++17, the above code would be ill-formed. What you have discovered is a compiler bug in GCC. The standard is very clear on this:

> The definition of a constexpr function shall satisfy the following requirements:
> - it shall not be virtual;
> - [...]

- [dcl.constexpr] p3

> If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) as Base​::​vf is declared, then Derived​::​vf is also virtual (whether or not it is so declared) and it overrides<sup>111</sup> Base​::​vf.

- [class.virtual] p2

From these two sections we can conclude that constexpr virtual functions are disallowed, and any function that overrides a virtual function also cannot be constexpr, because it is implicitly virtual.

GCC Diagnostics

Interestingly enough, we get no diagnostics for GetSize being virtual constexpr, even though it is a virtual function according to the standard.

// implicitly virtual because of it overrides SizedObject::GetSize
constexpr size_t GetSize() const override final { return s; }

If we mark the function virtual redundantly, we do get diagnostics:

constexpr virtual size_t GetSize() const override final { return s; }

> cpp
&gt; &lt;source&gt;:14:15: warning: member &#39;GetSize&#39; can be declared both &#39;virtual&#39; and &gt; &#39;constexpr&#39; only in &#39;-std=c++20&#39; or &#39;-std=gnu++20&#39; [-Wc++20-extensions]
&gt; 14 | constexpr virtual size_t GetSize() const override final { return s; }
&gt; | ~~~~~~~~~ ^~~~~~~
&gt;

MSVC Diagnostics

As for:
> subsidiary question: why does msvc issue the cannot result in a constant expression error?

Admittedly, this is not a very intuitive error message. It's simply what MSVC outputs for all cases where a function cannot be constexpr because of its signature. You get the same message when making the return type of a constexpr function std::vector&lt;int&gt;.

In general, constexpr means that at least for some arguments, a function has to be able to result in a constant expression. If the function signature makes that impossible, then specifying constexpr is wrong.

C++20

C++20 lifted these restrictions on constexpr virtual functions, so the above code would be well-formed.

huangapple
  • 本文由 发表于 2023年6月8日 21:51:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76432569.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定