Description
Title: constexpr
without ==
.
Description:
In reading P2996R0 Reflection for C++26 2.3 List of Types to List of Sizes,
I couldn't help but wonder,
how would we declare an uninitialized constexpr
variable in Cpp2.
This isn't a thing in Cpp1, yet.
But I think it's been mentioned as a possible relaxation to constexpr
,
including modifiable constexpr
globals (per TU?).
But there's actually a context that already affects Cpp2.
An @interface
with a constexpr
function (https://compiler-explorer.com/z/aro9K15b1):
struct X {
constexpr virtual char f() const = 0;
};
struct Y : X {
constexpr char f() const override { return 'Y'; }
};
constexpr char call_f(const X* x) { return x->f(); }
static_assert([y=Y{}] { return call_f(&y); }() == 'Y');
In Cpp2, the best approximation is (https://cpp2.godbolt.org/z/7Gha4YdK7):
X: @interface type = {
f: (this) -> char;
}
Y: type = {
this: X = ();
operator=: (out this) == { }
f: (override this) -> char == 'Y';
}
call_f: (x: * const X) -> char == x*.f();
main: () = {
static_assert(:() call_f(Y()$&);() == 'Y');
}
// Hack for `constexpr`.
namespace cpp2 {
constexpr auto assert_not_null(auto&& p CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) requires true
{
return CPP2_FORWARD(p);
}
}
But that errors with:
main.cpp2:11:17: error: static assertion expression is not an integral constant expression
11 | static_assert([_0 = Y()]() -> auto { return call_f(&_0); }() == 'Y');
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp2:11:17: note: non-literal type '(lambda at main.cpp2:11:17)' cannot be used in a constant expression
1 error generated.
It's not possible for X
to declare a constexpr default constructor or f
.
We could attempt using @polymorphic_base
.
In Cpp1 (https://compiler-explorer.com/z/Y653WPM7j):
struct X {
constexpr virtual char f() const { return 'X'; }
};
struct Y : X {
constexpr char f() const override { return 'Y'; }
};
constexpr char call_f(const X* x) { return x->f(); }
static_assert([x=X{}] { return call_f(&x); }() == 'X');
static_assert([y=Y{}] { return call_f(&y); }() == 'Y');
In Cpp2 (https://cpp2.godbolt.org/z/4MYv5E9vz):
X: @polymorphic_base type = {
operator=: (out this) == { }
operator=: (virtual move this) == { }
f: (virtual this) -> char == 'X';
}
Y: type = {
this: X = ();
operator=: (out this) == { }
f: (override this) -> char == 'Y';
}
call_f: (x: * const X) -> char == x*.f();
main: () = {
static_assert(:() call_f(X()$&);() == 'X');
static_assert(:() call_f(Y()$&);() == 'Y');
}
// Hack for `constexpr`.
namespace cpp2 {
constexpr auto assert_not_null(auto&& p CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) requires true
{
return CPP2_FORWARD(p);
}
}
That's a big jump in the API of X
(a variable can be instantiated,
calling f
on isn't a compile-time error,
calling std::terminate()
in f
helps at compile-time but otherwise delays to runtime).
And it happens to work because the return type is a mere char
(it could be a more complicated data type,
the data-less polymorphic base might need the help of globals).
Minimal reproducer (https://cpp2.godbolt.org/z/7Gha4YdK7):
X: @interface type = {
f: (this) -> char;
}
Y: type = {
this: X = ();
operator=: (out this) == { }
f: (override this) -> char == 'Y';
}
call_f: (x: * const X) -> char == x*.f();
main: () = {
static_assert(:() call_f(Y()$&);() == 'Y');
}
// Hack for `constexpr`.
namespace cpp2 {
constexpr auto assert_not_null(auto&& p CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) requires true
{
return CPP2_FORWARD(p);
}
}
Commands:
cppfront main.cpp2
clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -I . main.cpp
Expected result:
Consideration to constexpr
without ==
.
It might be relevant to some users today.
Cpp1 language evolution might also make it very relevant.
Herb has mentioned limiting global variables to constexpr
.
That could make x: int;
at namespace scope an uninitialized constexpr
variable.
But that wouldn't help with other entities that can be left uninitialized.
Actual result and error:
Cpp2 lowered to Cpp1:
//=== Cpp2 type declarations ====================================================
#include "cpp2util.h"
class X;
class Y;
//=== Cpp2 type definitions and function declarations ===========================
class X {
public: [[nodiscard]] virtual auto f() const -> char = 0;
public: virtual ~X() noexcept;
public: X() = default;
public: X(X const&) = delete; /* No 'that' constructor, suppress copy */
public: auto operator=(X const&) -> void = delete;
};
class Y: public X {
public: constexpr explicit Y();
public: [[nodiscard]] constexpr auto f() const -> char override;
public: Y(Y const&) = delete; /* No 'that' constructor, suppress copy */
public: auto operator=(Y const&) -> void = delete;
};
[[nodiscard]] constexpr auto call_f(X const* x) -> char;
auto main() -> int;
//=== Cpp2 function definitions =================================================
X::~X() noexcept{}
constexpr Y::Y()
: X{ }
{}
[[nodiscard]] constexpr auto Y::f() const -> char { return 'Y'; }
[[nodiscard]] constexpr auto call_f(X const* x) -> char { return CPP2_UFCS_0(f, (*cpp2::assert_not_null(x))); }
auto main() -> int{
static_assert([_0 = Y()]() -> auto { return call_f(&_0); }() == 'Y');
}
Output:
main.cpp2:11:17: error: static assertion expression is not an integral constant expression
11 | static_assert([_0 = Y()]() -> auto { return call_f(&_0); }() == 'Y');
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp2:11:17: note: non-literal type '(lambda at main.cpp2:11:17)' cannot be used in a constant expression
1 error generated.
See also:
- Doing away with
=
and==
: Dropping one extra `=` from declarations to make them less divergent from Cpp1 syntax #742. f: (x) x;
is neverconstexpr
: Terser function syntax? #714 (reply in thread).- Entities that can be left uninitialized: [SUGGESTION] Add support for defining type aliases. #273 (comment).