c++thisc++-concepts

Concepts and "deducing this"


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?


Solution

  • 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).