这个 constexpr 虚函数技术是否违反了任何 C++11/C++14 规则?
Does this constexpr virtual function technique violate any C++11/C++14 rule?
前几天我在阅读 C++ 文档时注意到,虽然字面量类型不能有虚拟成员,但这并不妨碍它们实现 虚拟成员。或者至少我是这么理解的。
这是我一直在玩的一段代码:
#include <cassert>
// Some forward declarations:
enum class literal_id;
struct literal_base;
struct literal_a;
struct literal_b;
// Now some definitions:
enum class literal_id {
a, b
};
struct literal_base {
virtual literal_id method() const noexcept = 0;
};
struct literal_a : public literal_base {
constexpr literal_id method() const noexcept final { return literal_id::a; }
constexpr operator literal_b() const noexcept;
};
struct literal_b : public literal_base {
constexpr literal_id method() const noexcept final { return literal_id::b; }
constexpr operator literal_a() const noexcept;
};
constexpr literal_a::operator literal_b() const noexcept { return literal_b(); }
constexpr literal_b::operator literal_a() const noexcept { return literal_a(); }
// Some test methods
literal_id process_literal_base(literal_base const& l) { return l.method(); }
constexpr literal_id process_literal_a(literal_a const& l) { return l.method(); }
constexpr literal_id process_literal_b(literal_b const& l) { return l.method(); }
// Some test variables
constexpr auto a = literal_a();
constexpr auto b = literal_b();
int main() {
// Compile-time tests, all ok
static_assert(process_literal_a(b) == literal_id::a, "");
static_assert(process_literal_b(a) == literal_id::b, "");
// Runtime tests, all ok
assert(process_literal_base(a) == literal_id::a);
assert(process_literal_base(b) == literal_id::b);
return 0;
}
一些备注:
- 我有一个基 class
literal_base
带有隐式(因此是微不足道的)析构函数,因为它的子 class 的 none 应该除了一个普通的析构函数——毕竟它们是文字类型。
literal_base
有一个用于测试的 method
函数,但目的是让它有尽可能多的 纯虚拟 函数(非-virtual final 函数也有效)。
- 请注意,子 class 中的
method
覆盖标记为 final
,即使它们未标记为 virtual
。这只是为了让编译器静音,因为那些 classes 应该是叶子(在它们的继承树中)或者没有被覆盖的函数。 (当 final
说明符尚不存在时,所有这些都与虚函数最终实现的 C++11 之前的未定义行为语义有关。)
- 创建
process_*
函数是为了帮助在编译和运行时断言实现的正确性。
- 我也玩过值语义,完全没有理由,一切都很好:)
一些相关的definitions for literal types:
...
possibly cv-qualified class type that has all of the following properties:
- (1) has a trivial destructor. [[ they have (the subclasses of
literal_base
, I mean) ]]
- (2) is either
- (2.1) an aggregate type, [[ not applicable ]]
- (2.2) a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor, [[ it has, but just because none of the classes have an explicit constructor; but it's easy to achieve ]]
- (2.3) a closure type (since C++17) [[ not applicable ]]
- (3) for unions, at least one non-static data member is of non-volatile literal type, [[ not applicable ]]
- (4) for non-unions, all non-static data members and base classes are of non-volatile literal types. (since C++17) [[ no
volatile
in the example, and neither should volatile
be used in a real application; also, literal_base
's subclasses are supposed to be literal types, so this rule must (and can) be applied ]]
- (5) all non-static data members and base classes are of non-volatile literal types. [[ same as (4), basically ]]
现在一些definitions for constexpr functions:
- it must not be virtual [[ none of the subclasses have virtual functions; all of them are final, and therefore their locations are known without the need for e.g. vtables ]]
- (...)
我的假设对吗?有没有我忽略的规范?
[dcl.constexpr]中的规则很清楚:
The definition of a constexpr
function shall satisfy the following requirements:
— it shall not be virtual (10.3);
literal_a::method
和 literal_b::method
都是 virtual
,因为它们都覆盖了 literal_base::method
,即 virtual
。因此,他们不能是constexpr.
,他们是final
也没关系。程序格式错误。
它 是 真实的,但允许文字类型具有 virtual
成员函数。
前几天我在阅读 C++ 文档时注意到,虽然字面量类型不能有虚拟成员,但这并不妨碍它们实现 虚拟成员。或者至少我是这么理解的。
这是我一直在玩的一段代码:
#include <cassert>
// Some forward declarations:
enum class literal_id;
struct literal_base;
struct literal_a;
struct literal_b;
// Now some definitions:
enum class literal_id {
a, b
};
struct literal_base {
virtual literal_id method() const noexcept = 0;
};
struct literal_a : public literal_base {
constexpr literal_id method() const noexcept final { return literal_id::a; }
constexpr operator literal_b() const noexcept;
};
struct literal_b : public literal_base {
constexpr literal_id method() const noexcept final { return literal_id::b; }
constexpr operator literal_a() const noexcept;
};
constexpr literal_a::operator literal_b() const noexcept { return literal_b(); }
constexpr literal_b::operator literal_a() const noexcept { return literal_a(); }
// Some test methods
literal_id process_literal_base(literal_base const& l) { return l.method(); }
constexpr literal_id process_literal_a(literal_a const& l) { return l.method(); }
constexpr literal_id process_literal_b(literal_b const& l) { return l.method(); }
// Some test variables
constexpr auto a = literal_a();
constexpr auto b = literal_b();
int main() {
// Compile-time tests, all ok
static_assert(process_literal_a(b) == literal_id::a, "");
static_assert(process_literal_b(a) == literal_id::b, "");
// Runtime tests, all ok
assert(process_literal_base(a) == literal_id::a);
assert(process_literal_base(b) == literal_id::b);
return 0;
}
一些备注:
- 我有一个基 class
literal_base
带有隐式(因此是微不足道的)析构函数,因为它的子 class 的 none 应该除了一个普通的析构函数——毕竟它们是文字类型。 literal_base
有一个用于测试的method
函数,但目的是让它有尽可能多的 纯虚拟 函数(非-virtual final 函数也有效)。- 请注意,子 class 中的
method
覆盖标记为final
,即使它们未标记为virtual
。这只是为了让编译器静音,因为那些 classes 应该是叶子(在它们的继承树中)或者没有被覆盖的函数。 (当final
说明符尚不存在时,所有这些都与虚函数最终实现的 C++11 之前的未定义行为语义有关。) - 创建
process_*
函数是为了帮助在编译和运行时断言实现的正确性。 - 我也玩过值语义,完全没有理由,一切都很好:)
一些相关的definitions for literal types:
... possibly cv-qualified class type that has all of the following properties:
- (1) has a trivial destructor. [[ they have (the subclasses of
literal_base
, I mean) ]]- (2) is either
- (2.1) an aggregate type, [[ not applicable ]]
- (2.2) a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor, [[ it has, but just because none of the classes have an explicit constructor; but it's easy to achieve ]]
- (2.3) a closure type (since C++17) [[ not applicable ]]
- (3) for unions, at least one non-static data member is of non-volatile literal type, [[ not applicable ]]
- (4) for non-unions, all non-static data members and base classes are of non-volatile literal types. (since C++17) [[ no
volatile
in the example, and neither shouldvolatile
be used in a real application; also,literal_base
's subclasses are supposed to be literal types, so this rule must (and can) be applied ]]- (5) all non-static data members and base classes are of non-volatile literal types. [[ same as (4), basically ]]
现在一些definitions for constexpr functions:
- it must not be virtual [[ none of the subclasses have virtual functions; all of them are final, and therefore their locations are known without the need for e.g. vtables ]]
- (...)
我的假设对吗?有没有我忽略的规范?
[dcl.constexpr]中的规则很清楚:
The definition of a
constexpr
function shall satisfy the following requirements:
— it shall not be virtual (10.3);
literal_a::method
和 literal_b::method
都是 virtual
,因为它们都覆盖了 literal_base::method
,即 virtual
。因此,他们不能是constexpr.
,他们是final
也没关系。程序格式错误。
它 是 真实的,但允许文字类型具有 virtual
成员函数。