hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.39k stars 232 forks source link

[BUG] `@enum` and `@flag_enum` are not models of `std::regular` #873

Closed JohelEGP closed 8 months ago

JohelEGP commented 8 months ago

Title: @enum and @flag_enum are not models of std::regular.

Description:

From https://github.com/hsutter/cppfront/issues/761#issuecomment-1778298138:

In commit b589f5d25e5acdbfd94791e23c0ba0f6fdcd59c6, the enum metafunctions had to stop using basic_value to explicitly opt-into the == syntax for constexpr.

The change didn't add the default constructor. I except @flag_enums to model std::regular. Those already generate a none value initialized with 0 that is the perfect default value.

I was also expecting @enums to model std::regular. But those don't have a none value, and the first enumerator, which is initialized with 0, doesn't necessarily represent a default value. But regularity just makes it easier to work with in some contexts.

Minimal reproducer (https://cpp2.godbolt.org/z/v3Ec5zM7r):

engine: @enum type = {
  off;
  on;
}
perms: @flag_enum type = {
  read;
  write;
}
main: () = {
  static_assert(std::default_initializable<engine>);
  static_assert(std::default_initializable<perms>);
}

Commands: ```bash cppfront main.cpp2 clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -Werror=unused-value -Werror=unused-parameter -I . main.cpp ```

Expected result: Both types to model std::regular.

Actual result and error: Both static assertions failed.

Cpp2 lowered to Cpp1: ```C++ //=== Cpp2 type declarations ==================================================== #include "cpp2util.h" #line 1 "/app/example.cpp2" class engine; #line 2 "/app/example.cpp2" #line 5 "/app/example.cpp2" class perms; //=== Cpp2 type definitions and function declarations =========================== #line 1 "/app/example.cpp2" class engine { private: cpp2::i8 _value; private: constexpr engine(cpp2::in _val); private: constexpr auto operator=(cpp2::in _val) -> engine& ; public: [[nodiscard]] constexpr auto get_raw_value() const& -> cpp2::i8; public: constexpr engine(engine const& that); public: constexpr auto operator=(engine const& that) -> engine& ; public: constexpr engine(engine&& that) noexcept; public: constexpr auto operator=(engine&& that) noexcept -> engine& ; public: [[nodiscard]] auto operator<=>(engine const& that) const& -> std::strong_ordering = default; public: static const engine off; public: static const engine on; public: [[nodiscard]] auto to_string() const& -> std::string; #line 4 "/app/example.cpp2" }; class perms { private: cpp2::u8 _value; private: constexpr perms(cpp2::in _val); private: constexpr auto operator=(cpp2::in _val) -> perms& ; public: [[nodiscard]] constexpr auto get_raw_value() const& -> cpp2::u8; public: constexpr perms(perms const& that); public: constexpr auto operator=(perms const& that) -> perms& ; public: constexpr perms(perms&& that) noexcept; public: constexpr auto operator=(perms&& that) noexcept -> perms& ; public: [[nodiscard]] auto operator<=>(perms const& that) const& -> std::strong_ordering = default; public: constexpr auto operator|=(perms const& that) & -> void; public: constexpr auto operator&=(perms const& that) & -> void; public: constexpr auto operator^=(perms const& that) & -> void; public: [[nodiscard]] constexpr auto operator|(perms const& that) const& -> perms; public: [[nodiscard]] constexpr auto operator&(perms const& that) const& -> perms; public: [[nodiscard]] constexpr auto operator^(perms const& that) const& -> perms; public: [[nodiscard]] constexpr auto has(perms const& that) & -> bool; public: constexpr auto set(perms const& that) & -> void; public: constexpr auto clear(perms const& that) & -> void; public: static const perms read; public: static const perms write; public: static const perms none; public: [[nodiscard]] auto to_string() const& -> std::string; #line 8 "/app/example.cpp2" }; auto main() -> int; //=== Cpp2 function definitions ================================================= #line 1 "/app/example.cpp2" #line 1 "/app/example.cpp2" constexpr engine::engine(cpp2::in _val) : _value{ cpp2::unsafe_narrow(_val) } { } constexpr auto engine::operator=(cpp2::in _val) -> engine& { _value = cpp2::unsafe_narrow(_val); return *this; } [[nodiscard]] constexpr auto engine::get_raw_value() const& -> cpp2::i8 { return _value; } constexpr engine::engine(engine const& that) : _value{ that._value }{} constexpr auto engine::operator=(engine const& that) -> engine& { _value = that._value; return *this;} constexpr engine::engine(engine&& that) noexcept : _value{ std::move(that)._value }{} constexpr auto engine::operator=(engine&& that) noexcept -> engine& { _value = std::move(that)._value; return *this;} inline CPP2_CONSTEXPR engine engine::off = 0; inline CPP2_CONSTEXPR engine engine::on = 1; [[nodiscard]] auto engine::to_string() const& -> std::string{ if ((*this) == off) {return "off"; } if ((*this) == on) {return "on"; } return "invalid engine value"; } constexpr perms::perms(cpp2::in _val) : _value{ cpp2::unsafe_narrow(_val) } { } constexpr auto perms::operator=(cpp2::in _val) -> perms& { _value = cpp2::unsafe_narrow(_val); return *this; } [[nodiscard]] constexpr auto perms::get_raw_value() const& -> cpp2::u8 { return _value; } constexpr perms::perms(perms const& that) : _value{ that._value }{} constexpr auto perms::operator=(perms const& that) -> perms& { _value = that._value; return *this;} constexpr perms::perms(perms&& that) noexcept : _value{ std::move(that)._value }{} constexpr auto perms::operator=(perms&& that) noexcept -> perms& { _value = std::move(that)._value; return *this;} constexpr auto perms::operator|=(perms const& that) & -> void { _value |= that._value; } constexpr auto perms::operator&=(perms const& that) & -> void { _value &= that._value; } constexpr auto perms::operator^=(perms const& that) & -> void { _value ^= that._value; } [[nodiscard]] constexpr auto perms::operator|(perms const& that) const& -> perms { return _value | that._value; } [[nodiscard]] constexpr auto perms::operator&(perms const& that) const& -> perms { return _value & that._value; } [[nodiscard]] constexpr auto perms::operator^(perms const& that) const& -> perms { return _value ^ that._value; } [[nodiscard]] constexpr auto perms::has(perms const& that) & -> bool { return _value & that._value; } constexpr auto perms::set(perms const& that) & -> void { _value |= that._value; } constexpr auto perms::clear(perms const& that) & -> void { _value &= ~that._value; } inline CPP2_CONSTEXPR perms perms::read = 1; inline CPP2_CONSTEXPR perms perms::write = 2; inline CPP2_CONSTEXPR perms perms::none = 0; [[nodiscard]] auto perms::to_string() const& -> std::string{ std::string _ret {"("}; std::string _comma {}; if ((*this) == none) {return "(none)"; } if (((*this) & read) == read) {_ret += _comma + "read";_comma = ", ";} if (((*this) & write) == write) {_ret += _comma + "write";_comma = ", ";} return _ret + ")"; } #line 9 "/app/example.cpp2" auto main() -> int{ static_assert(std::default_initializable); static_assert(std::default_initializable); } ```

Output: ```output Step cmake returned: 0 -- The CXX compiler identification is Clang 18.0.0 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /opt/compiler-explorer/clang-trunk/bin/clang++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done (0.5s) -- Generating done (0.0s) -- Build files have been written to: /app/build Step build returned: 1 [1/3] Generating main.cpp main.cpp2... ok (all Cpp2, passes safety checks) [2/3] Building CXX object CMakeFiles/main.dir/main.cpp.o FAILED: CMakeFiles/main.dir/main.cpp.o /opt/compiler-explorer/clang-trunk/bin/clang++ --gcc-toolchain=/opt/compiler-explorer/gcc-snapshot -I/app -I/app -std=c++23 -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -Werror=unused-value -Werror=unused-parameter -stdlib=libc++ -Wno-read-modules-implicitly -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /app/build/main.cpp main.cpp2:10:17: error: static assertion failed 10 | static_assert(std::default_initializable); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ main.cpp2:10:17: note: because 'engine' does not satisfy 'default_initializable' /opt/compiler-explorer/clang-trunk-20231205/bin/../include/c++/v1/__concepts/constructible.h:35:33: note: because 'engine' does not satisfy 'constructible_from' 35 | concept default_initializable = constructible_from<_Tp> && requires { _Tp{}; } && __default_initializable<_Tp>; | ^ /opt/compiler-explorer/clang-trunk-20231205/bin/../include/c++/v1/__concepts/constructible.h:27:51: note: because 'is_constructible_v' evaluated to false 27 | concept constructible_from = destructible<_Tp> && is_constructible_v<_Tp, _Args...>; | ^ main.cpp2:11:17: error: static assertion failed 11 | static_assert(std::default_initializable); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ main.cpp2:11:17: note: because 'perms' does not satisfy 'default_initializable' /opt/compiler-explorer/clang-trunk-20231205/bin/../include/c++/v1/__concepts/constructible.h:35:33: note: because 'perms' does not satisfy 'constructible_from' 35 | concept default_initializable = constructible_from<_Tp> && requires { _Tp{}; } && __default_initializable<_Tp>; | ^ /opt/compiler-explorer/clang-trunk-20231205/bin/../include/c++/v1/__concepts/constructible.h:27:51: note: because 'is_constructible_v' evaluated to false 27 | concept constructible_from = destructible<_Tp> && is_constructible_v<_Tp, _Args...>; | ^ 2 errors generated. ninja: build stopped: subcommand failed. ```
JohelEGP commented 8 months ago

But regularity just makes it easier to work with in some contexts.

The most relevant guidelines are

hsutter commented 8 months ago

Thanks!