I'm exploring the "deducing this" feature and I'm having trouble understanding how to use it correctly with concepts. Here's what I have:
#include <concepts>
struct X {
X() = default;
X& operator+=(int x) { value += x; return *this; }
int value = 0;
};
struct Y {
Y() = default;
template<typename Self> Self& operator+=(this Self& self, int x) {
self.value += x; return self;
}
int value = 0;
};
template<typename T, typename U>
concept has_addition_assignment =
std::is_lvalue_reference_v<T>
&& requires(T a, U b) { { a += b } -> std::same_as<T>; };
static_assert(has_addition_assignment<X&,int>); // OK
static_assert(!has_addition_assignment<const X&,int>); // OK
static_assert(has_addition_assignment<Y&,int>); // OK
// static_assert(!has_addition_assignment<const Y&,int>); // Fails
int
main()
{
// Not allowed.
// const Y y;
// y += 1;
}
The concept is trying to ensure that X x; x += 1;
works, but const X x; x += 1
does not. This works for X
but not Y
, despite const Y y; y += 1;
failing.
I believe my concept is written incorrectly, or perhaps +=
in Y
doesn't express my intent correctly. What's the best way to check that it's not okay to add something to a const object when using deducing this?
Concepts can only detect whether a declaration represents valid code, not whether the definition behind that declaration works.
X::operator +=
has a declaration that takes this
as non-const
. There is no other overload of the function. As such, at the declaration level, any attempt to call this function with a const
value will fail due to a lack of an appropriate overload that can take a const
this
.
Because you declared Y::operator +=
as an unconstrained template, Self
could be anything. It could be a const
type, and the rest of the declaration would neither know nor care. Attempting to call the function on a const
object will still fail, but it will only fail due to the definition being faulty (attempting to modify a const
object). As far as the declaration is concerned, it is legitimate.
And concepts only care about the declaration.
So if you intend for your Self
function to not be appropriate for const
parameters, you have to explicitly code that into the declaration. You could use a concept that fails if the type is a const
type, but a simpler way is to explicitly = delete
the const
version:
template<typename Self> Self& operator+=(this Self const& self, int x) = delete;
Since that's a better match, it will be used if a const
type is being employed. And that will cause an overload resolution failure (a declaration-based test).