AK: Some Variant cleanup after c++26#26547
Conversation
| consteval IndexType index_of() | ||
| { | ||
| return VariantIndexOf<T, IndexType, 0, Ts...> {}(); | ||
| // Note: This ignores ambiguous lookups, and instead returns the first match |
There was a problem hiding this comment.
ambiguous? how can there be an ambiguous lookup?
There was a problem hiding this comment.
as in, for index_of<int>(int, int, int) it will always return 0
There was a problem hiding this comment.
the only valid index is the first one in any case, MergeAndDeduplicate guarantees that.
There was a problem hiding this comment.
Should I drop the comment, or is this fine as is?
f0af688 to
4ff1fe0
Compare
|
Whoops pack indexing only works in gcc-15+ which ubuntu doesnt ship for 22.04, so it's off limits |
d32d9bc to
9ede9e3
Compare
|
Happy to have @alimpfard review this, but in case he's busy: From a distance, commit 3 looks nice and the tests in commit 2 are nice, but commit 2 is about the same amount of code lhs and rhs, so it feels a bit like a sideways change. |
alimpfard
left a comment
There was a problem hiding this comment.
Largely LGTM, and I really like that the hacky constructors are gone (yay)
| // Blank<T> is a unique replacement for T, if T is a duplicate type. | ||
| template<typename T> | ||
| struct Blank { | ||
| void operator()() const; |
There was a problem hiding this comment.
This will probably end up a bottleneck if the compiler doesn't inline it, have you checked for perf?
There was a problem hiding this comment.
Ok, been a week or two,
but iirc it should never actually be called, especially as it is not implemented, it only exists
Actually, does Blank actually need it?
There was a problem hiding this comment.
Ah yeah, its needed as the replacement happens late-ish, so the lookup thing inherits from it and tries to export the operator
| // NOTE: This always allows narrowing, | ||
| // The stl version does not allow narrowing conversions | ||
| // main points where we need it are instantiations with literal 0s, | ||
| // which we could feasibly check for with some more template magic and is_constant_p. |
There was a problem hiding this comment.
I think narrowing is fine if it's unambiguous, and I assume this will just complain about ambiguity if it is indeed ambiguous?
Maybe a test about that would be nice to have.
There was a problem hiding this comment.
I added tests which would complain if we didnt narrow,
and one that would behave differently if we narrow wrongly (I think)
Not sure how to test true ambiguity except for compilation failures
There was a problem hiding this comment.
maybe !requires { ... }? not sure.
just the narrowing tests should be good, that's what I intended anyway.
|
|
||
| // FIXME: Not sure why we need the constraint here to avoid recursion, | ||
| // The variant should not be able to contain it self, so a constructibility check should | ||
| // be enough? |
There was a problem hiding this comment.
iirc the "recursion" thing was to make sure the compiler wouldn't attempt to use this for perfect forwarding instead of the copy/move ctor (should be possible to name a conversion operator from Variant<T, ...> to T)
I don't think it's actually possible to invoke this with a Variant<Ts...> anyhow, so should be ok to remove that constraint.
There was a problem hiding this comment.
Well I tried to remove the constrained, and it didn't like it,
if you have a diff, I'd be happy to apply it
There was a problem hiding this comment.
what didn't like it? it builds fine for me
diff --git a/AK/Variant.h b/AK/Variant.h
index 3f25043764..d1b0e9252c 100644
--- a/AK/Variant.h
+++ b/AK/Variant.h
@@ -246,8 +246,7 @@ public:
// The variant should not be able to contain it self, so a constructibility check should
// be enough?
template<typename T>
- requires(!IsSame<RemoveCVReference<T>, Variant>
- && (IsConstructible<Ts, T> || ...))
+ requires(IsConstructible<Ts, T> || ...)
Variant(T&& t)
{
using BestOverload = BestMatch<T>;There was a problem hiding this comment.
/home/leon/serenity/Meta/Lagom/../../AK/Variant.h:249:14: error: satisfaction of constraint 'IsConstructible<Ts, T>' depends on itself
249 | requires(IsConstructible<Ts, T> || ...)
| ^~~~~~~~~~~~~~~~~~~~~~
/home/leon/serenity/Meta/Lagom/../../AK/Variant.h:249:14: note: while substituting template arguments into constraint expression here
249 | requires(IsConstructible<Ts, T> || ...)
| ^~~~~~~~~~~~~~~~~~~~~~
/home/leon/serenity/Meta/Lagom/../../AK/StdLibExtraDetails.h:481:60: note: while checking constraint satisfaction for template 'Variant<Web::Geometry::ParsedMatrix, Web::WebIDL::SimpleException, JS::NonnullGCPtr<Web::WebIDL::DOMException>, JS::Completion>' required here
481 | inline constexpr bool IsConstructible = requires { ::new T(declval<Args>()...); };
| ^~~~~~~
/home/leon/serenity/Meta/Lagom/../../AK/StdLibExtraDetails.h:481:60: note: while substituting deduced template arguments into function template 'Variant' [with T = const AK::Variant<Web::Geometry::ParsedMatrix, Web::WebIDL::SimpleException, JS::NonnullGCPtr<Web::WebIDL::DOMException>, JS::Completion> &]
/home/leon/serenity/Meta/Lagom/../../AK/StdLibExtraDetails.h:481:52: note: in instantiation of requirement here
481 | inline constexpr bool IsConstructible = requires { ::new T(declval<Args>()...); };
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/leon/serenity/Meta/Lagom/../../AK/Variant.h:249:14: note: in instantiation of variable template specialization 'AK::Detail::IsConstructible<Web::Geometry::ParsedMatrix, const AK::Variant<Web::Geometry::ParsedMatrix, Web::WebIDL::SimpleException, JS::NonnullGCPtr<Web::WebIDL::DOMException>, JS::Completion> &>' requested here
249 | requires(IsConstructible<Ts, T> || ...)
| ^
/home/leon/serenity/Meta/Lagom/../../AK/Variant.h:249:14: note: while substituting template arguments into constraint expression here
249 | requires(IsConstructible<Ts, T> || ...)
| ^~~~~~~~~~~~~~~~~~~~~~
/home/leon/serenity/Meta/Lagom/../../AK/Variant.h:214:8: note: while checking constraint satisfaction for template 'Variant<Web::Geometry::ParsedMatrix, Web::WebIDL::SimpleException, JS::NonnullGCPtr<Web::WebIDL::DOMException>, JS::Completion>' required here
214 | struct Variant {
| ^~~~~~~
/home/leon/serenity/Meta/Lagom/../../AK/Variant.h:214:8: note: in instantiation of function template specialization 'AK::Variant<Web::Geometry::ParsedMatrix, Web::WebIDL::SimpleException, JS::NonnullGCPtr<Web::WebIDL::DOMException>, JS::Completion>::Variant<const AK::Variant<Web::Geometry::ParsedMatrix, Web::WebIDL::SimpleException, JS::NonnullGCPtr<Web::WebIDL::DOMException>, JS::Completion> &>' requested here
/home/leon/serenity/Userland/Libraries/LibWeb/Geometry/DOMMatrixReadOnly.cpp:46:27: note: in instantiation of template class 'Web::WebIDL::ExceptionOr<Web::Geometry::ParsedMatrix>' requested here
46 | auto result = TRY(parse_dom_matrix_init_string(realm, init_value.get<String>()));
| ^
4 errors generated.
[1264/3300] Building CXX object Userland/Libraries/LibWeb/CMakeFiles/LibWeb.dir/HTML/CanvasRenderingContext2D.cpp.o
idk
There was a problem hiding this comment.
Huh, compiler difference maybe?
anyway it's fine to keep if it helps I guess.
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! |
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! |
|
This pull request has been closed because it has not had recent activity. Feel free to re-open if you wish to still contribute these changes. Thank you for your contributions! |
9ede9e3 to
0a78b88
Compare
| @@ -8,6 +8,7 @@ | |||
|
|
|||
There was a problem hiding this comment.
Could you give more details on what's the new approach in the commit description.
There was a problem hiding this comment.
Added a description next to the overload resolution helper, as well as in the commit message
not sure how understandable it is, needed to look hard at it for 5 minutes again myself
| @@ -14,66 +14,49 @@ | |||
|
|
|||
There was a problem hiding this comment.
It seems that the commit is not atomic.
It would be nice to split them in different commits with proper descriptions.
(If some are independents of the previous changes in the PR, I'll more comfortably merge a small Variant PR)
There was a problem hiding this comment.
Split it up, though the commits sound more feng-shui now
(possibly to many commata as well)
0a78b88 to
d11a870
Compare
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! |
|
This pull request has been closed because it has not had recent activity. Feel free to re-open if you wish to still contribute these changes. Thank you for your contributions! |
e124f21 to
8a5828f
Compare
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! |
Otherwise we may end up in a delegation loop
bcda3a0 to
641b230
Compare
| // *: This is the reason for the forwarding reference in the arguments | ||
| template<typename U, typename = T> | ||
| requires(IsConstructible<T, U>) | ||
| auto operator()(T, U&&) const -> __IdentityType<T>; |
There was a problem hiding this comment.
The return type doesn't participate in type resolution anyway, why IdentityType?
There was a problem hiding this comment.
To allow ::Type Accesses (thats why the __ version)
But removing that access using the type directly seems to also be fine
(tbh not sure why the STL and implementors love the foo::type things?)
There was a problem hiding this comment.
not sure why anyone would care. the STL's foo::type nonsense is just leftover from when there was no templated dependent type aliases.
There was a problem hiding this comment.
Sure, removed that for now,
will need to add something like it back, iff I get index pass-through working though
| // Blank<T> is a unique replacement for T, if T is a duplicate type. | ||
| template<typename T> | ||
| struct Blank { | ||
| void operator()() const; |
There was a problem hiding this comment.
This has the wrong shape anyway, it seems to only ever be called with (T, U&&) no?
There was a problem hiding this comment.
Well it should only be nameable and its intentionally not callable,
I can make the rejection more explicit if you want/explain why it has not the correct shape
There was a problem hiding this comment.
what refers to this thing? why does it need to be named?
There was a problem hiding this comment.
InheritFromPacks tries to export it
There was a problem hiding this comment.
as it currently replaces the Overload, not the T in Overload
There was a problem hiding this comment.
it does try to export it, but for what?
operator()() existing when you expect to call operator()(T, U&&) is pointless, I just don't understand why this needs to exist?
There was a problem hiding this comment.
Is it better now, with the explicit disable?
(Note: using XYZ::function does not care for signature only for names)
| struct Variant | ||
| : public Detail::MergeAndDeduplicatePacks<Detail::VariantConstructors<Ts, Variant<Ts...>>...> { | ||
| struct Variant { | ||
| // FIXME: Can we get this to return the index as well? |
There was a problem hiding this comment.
yes, but there's no point. a single type identifies exactly the variant since we guarantee uniqueness by construction.
There was a problem hiding this comment.
Mainly to avoid the repeated lookup, as the contrutor chain lookup should be able to return an index
The current issue is that, when I add an index to the Base of "MergeAndDedup" it changes the template so the dedup does not work, and I have no clue how I make the deduplicator agnostic of that
There was a problem hiding this comment.
why should the lookup return an index though? the type is unique and it identifies the position.
There was a problem hiding this comment.
But all users want the position, so they do a position look up afterwards, which is a slight compile-time overhead, as we could get that info from the first lookup
There was a problem hiding this comment.
Added a commit where I managed to get the index from the initial lookup,
but I needed to change the dedup order, but I am not entirely sure if I did that in the correct manner,
so I would like a check on that before I squash that in
| } | ||
|
|
||
| // FIXME: Not sure why we need the `!IsSame` constraint here to avoid recursion, | ||
| // The variant should not be able to contain it self, so a constructibility check should |
There was a problem hiding this comment.
"can be constructed" still ends up reinstantiating this ctor infinitely to typecheck it, is that what you're looking for?
There was a problem hiding this comment.
Well last time, your compiler somehow did not need it,
but still no clue
| } | ||
|
|
||
| template<size_t I, typename Self> | ||
| constexpr auto& get(this Self&& self) |
There was a problem hiding this comment.
this is too permissive, you can get a valid reference to a part of a temporary object with it.
| }; | ||
|
|
||
| template<typename Ref, typename U> | ||
| using LikeT = __LikeT<Ref&&, U&>::Type; |
There was a problem hiding this comment.
If you're not sold on the name, how about CopyCVReference and add volatile to this?
There was a problem hiding this comment.
Not quite a copy, more an apply from, as this cannot remove a const
This is reverse engineered from how GCC does it as that looked like the most reasonable way of doing forward_like,
the cppref suggestion had some deduction issues
|
|
||
| static_assert(IsSame<int&, LikeT<long&, int>>); | ||
| static_assert(IsSame<int const&&, LikeT<long const&&, int&>>); | ||
| static_assert(IsSame<int const&, LikeT<long const&, int&&>>); |
There was a problem hiding this comment.
eh...probably the wrong place for these "tests"?
| } | ||
|
|
||
| template<typename Ref, typename T> | ||
| constexpr auto&& forward_like(T&& val) |
There was a problem hiding this comment.
why auto&& over decltype(auto)? is this the signature from the STL?
There was a problem hiding this comment.
yeah thats the signature cpprf suggests,
Not entirely sure what the difference between those two is, tbh
|
|
||
| Helper::construct<BestOverloadIndex>(m_data, forward<T>(value)); | ||
| m_index = BestOverloadIndex; | ||
|
|
There was a problem hiding this comment.
this change doesn't look like it belongs in this commit.
Previously we inherited from a set of constructors, which casted themselves to the final Variant type to initialize the wanted storage, which is very UB. Now we choose the best overload by checking for which Variant member <T> we can call a function taking a <T>, while passing in a fully qualified argument <U>, as passed into the Variant constructor.
No need to look up the index, we can directly check with `IsOneOf`
Also remove users of the old recursive version, which were mostly redundantly checking the index again. The remaining users have been renamed to be a bit more descriptive.
By just accessing the data in the Variant through the normal means, we avoid a bunch of dubious casts.
641b230 to
6273909
Compare
For constructing the Storage we use a sentinel value (`VariantIndex`) to pass along the desired depth/type into the constructor chain. This is done as we cant easily start the lifetime of the alternatives after the union was instantiated. Upcoming c++26 features should fix this (see trivial unions (P3074R7) and std::start_lifetime (P3726R2)) For move and copy assignment we destroy the active member, and then replace the whole storage, trivial unions may also make this nicer.
6273909 to
f57855a
Compare
| struct Variant | ||
| : public Detail::MergeAndDeduplicatePacks<Detail::VariantConstructors<Ts, Variant<Ts...>>...> { | ||
| struct Variant { | ||
| // FIXME: Can we get this to return the index as well? |
There was a problem hiding this comment.
Added a commit where I managed to get the index from the initial lookup,
but I needed to change the dedup order, but I am not entirely sure if I did that in the correct manner,
so I would like a check on that before I squash that in
| template<size_t I, typename... Ts, size_t... Js, typename... Qs> | ||
| struct InheritFromUniqueEntries<I, ParameterPack<Ts...>, IndexSequence<Js...>, Qs...> | ||
| : public BlankIfDuplicate<Ts, Conditional<Js <= I, ParameterPack<>, Qs>...>... { | ||
| : public BlankIfDuplicate<Ts, Conditional<(Js >= I), ParameterPack<>, Qs>...>... { |
There was a problem hiding this comment.
Is this the correct way of changing the order and has that any other effects, like perf
(The comment above likely needs adjusting)
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! |
While c++26 was not required for most of these, it did make it a bit easier at some points
This consists of 2 main parts:
This is step 1 of getting
constexprworking for it, and makes some more ugly parts of it disappearCC: @alimpfard as our local template and variant expert