It is generally known that type qualifiers (such as `const`

and `volatile`

in C++) can be regarded as a form of subtyping: for instance, `const T`

is a *supertype* of `T`

because the interface (available operations) of `T`

are strictly wider than those of `const T`

. Foster et al. call a qualifier **q** *positive* if **q** `T`

is a supertype of `T`

, and *negative* it if is the other way around. Without real loss of generality, in what follows we only consider negative qualifiers, where **q** `T`

is a *subtype* of (extends the interface of) `T`

.

C++23 explicit object parameters (coloquially known as "deducing `this`

") allow for a particularly concise and effective realization of user-defined qualifiers for class types beyond what the language provides natively. For instance, this is a syntactically complete implementation of qualifier `mut`

, the dual/inverse of `const`

(not to be confused with `mutable`

):

`template<typename T>`

struct mut: T

{

using T::T;

};

template<typename T>

T& as_const(T& x) { return x;}

template<typename T>

T& as_const(mut<T>& x) { return x;}

struct X

{

void foo() {}

void bar(this mut<X>&) {}

};

int main()

{

mut<X> x;

x.foo();

x.bar();

auto& y = as_const(x);

y.foo();

y.bar(); // error: cannot convert argument 1 from 'X' to 'mut<X> &'

X& z = x;

z.foo();

z.bar(); // error: cannot convert argument 1 from 'X' to 'mut<X> &'

}

The class `X`

has a regular (generally accessible) member function `foo`

and then `bar`

, which is only accessible to instances of the form `mut<X>`

. Access checking and implicit and explicit conversion between subtype `mut<X>`

and `mut<X>`

work as expected.

With some help fom Boost.Mp11, the idiom can be generalized to the case of several qualifiers:

`#include <boost/mp11/algorithm.hpp>`

#include <boost/mp11/list.hpp>

#include <type_traits>

template<typename T,typename... Qualifiers>

struct access: T

{

using qualifier_list=boost::mp11::mp_list<Qualifiers...>;

using T::T;

};

template<typename T, typename... Qualifiers>

concept qualified =

(boost::mp11::mp_contains<

typename std::remove_cvref_t<T>::qualifier_list,

Qualifiers>::value && ...);

// some qualifiers

struct mut;

struct synchronized;

template<typename T>

concept is_mut = qualified<T, mut>;

template<typename T>

concept is_synchronized = qualified<T, synchronized>;

struct X

{

void foo() {}

template<is_mut Self>

void bar(this Self&&) {}

template<is_synchronized Self>

void baz(this Self&&) {}

template<typename Self>

void qux(this Self&&)

requires qualified<Self, mut, synchronized>

{}

};

int main()

{

access<X, mut> x;

x.foo();

x.bar();

x.baz(); // error: associated constraints are not satisfied

x.qux(); // error: associated constraints are not satisfied

X y;

x.foo();

y.bar(); // error: associated constraints are not satisfied

access<X, mut, synchronized> z;

z.bar();

z.baz();

z.qux();

}

`int main()`

{

access<X, mut, synchronized> z;

//...

access<X, mut>& w=z; // error: cannot convert from

// 'access<X,mut,synchronized>'

// to 'access<X,mut> &'

}

`access<T,Qualifiers...>&`

converts to `T&`

, but not to `access<T,Qualifiers2...>&`

where `Qualifiers2`

is a subset of `Qualifiers`

(for the mathematically inclined, qualifiers **q**

_{1}, ... ,

**q**

_{N}over a type

`T`

induce a lattice of subtypes **Q**

`T`

, **Q**⊆ {

**q**

_{1}, ... ,

**q**

_{N}}, ordered by qualifier inclusion). Incurring undefined behavior, we could do the following:

`template<typename T,typename... Qualifiers>`

struct access: T

{

using qualifier_list=boost::mp11::mp_list<Qualifiers...>;

using T::T;

template<typename... Qualifiers2>

operator access<T, Qualifiers2...>&()

requires qualified<access, Qualifiers2...>

{

return reinterpret_cast<access<T, Qualifiers2...>&>(*this);

}

};

*syntactic*qualifier subtyping, but does not do anything towards enforcing the semantics associated to each qualifier: for instance,

`synchronized`

should lock a mutex automatically, and a qualifier associated to some particular invariant should assert it after each invocation to a qualifier-constraied member function. I don't know if this functionality can be more or less easily integrated into the presented framework: feedback on the matter is much welcome.