Compiler Status
Compiler support:
β
MSVC has "deducing this" support today.
β GCC does not have any support yet. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102609
π§ Clang does not have support but cor3ntin is working on it: llvm/llvm-project#59619
Overview
Today we write methods that consume this
as &&-qualified. For example:
constexpr T Option::unwrap() && noexcept {
return ::sus::move(*this).unwrap_unchecked(::sus::marker::unsafe_fn);
}
If the T
in Option<T>
is not sus::mem::CopyOrRef
, then this is the right thing to do. To call this method you must have an rvalue, either by construction or by sus::move()
.
However if the T
is CopyOrRef
, then Option<T>
is Copy
. And in that case, it would be nice to do as Rust would do. Calling a method that consumes this
would first do a copy, and call the method on that copy. In Rust you'd write such a thing like
fn unwrap(self) -> T {
unsafe { self.unwrap_unchecked() }
}
as there are no rvalue references in Rust.
In option.h we have a TODO which points out we can do this in C++ by writing a 2nd copy of every &&-qualified method as a const&-qualified method which requires sus::mem::CopyOrRef<T>
, as that is the condition that makes Option<T>
itself become Copy
.
However this means a 2nd copy of every method on Option more or less. Overloads suck.
Good news is C++23 introduces deducing this, which allows us to combined &, const&, &&, const&& qualified overloads into a single method.
template <class Self>
constexpr T Option::unwrap(this Self&& self) noexcept {
return ::sus::forward<Self>(self).unwrap_unchecked(::sus::marker::unsafe_fn);
}
The forward will preserve the reference type of self
and we have 1 method for all qualifiers. However this is not really what we want at all. We only want a &&-qualified method (and a const&-qualified one if CopyOrRef<T>
).
But there's another formulation of deducing this that received self
by value.
constexpr T Option::unwrap(this auto self) noexcept {
return ::sus::move(self).unwrap_unchecked(::sus::marker::unsafe_fn);
}
Here we would receive a new self
move-constructed from an rvalue, if the caller was calling a method on an rvalue. And if the caller was an lvalue, it will have to copy-constructed self
. This does exactly what we want, as it copies if Option
was Copy
.
The only downside here is the errors. We can't eliminate this method from attempting to be called on an lvalue when self
is not Copy. In fact it will instead fall back to C++'s internal traits of std::is_copy_constructible<Option<T>>
. Since Option makes a copy constructor iff it also makes a copy assignment operator, this ends up being equivalent, you just get an error about failure to copy if you did it wrong due to deleted copy constructor in Option instead of the nicer "this type is not Copy" that you can get from requiring sus::mem::CopyOrRef
.
Plan
So all this is to say, once all three compilers have deducing this, I think we should:
- In all cases that we have a &&-qualified method and we don't explicitly delete the const&-qualified overload (I don't think we have any of that),
- And when the class itself is or can be Copy,
- Convert the &&-qualified method to a
this auto self
.
If the class can not be Copy, or has a deleted const&-qualified overload, then convert &&-qualified methods to this Self&& self
as it's just a nicer syntax.
Similarly, convert all other qualified method, perhaps all methods in totality, over to their equivalent deducing this version. Be careful to not use auto&&
as the type of this
when you don't want to support all qualifiers.