I have some class A
that has some const
and some non-const
function, together with a fitting concept, say
class A {
public:
void modify() {/* ... */}
void print() const {/* ... */}
}
template<typename T>
concept LikeA = requires (T t, const T const_t) {
{ t.modify() } -> std::same_as<void>;
{ const_t.print() } -> std::same_as<void>;
}
static_assert(LikeA<A>);
A nice thing I noticed is that for some function taking const LikeA auto &a
below code is actually legal:
void use (const LikeA auto &a) {
a.print(); // fine, print is a const method
// a.modify(); // illegal, a is a const reference [at least for use(A a) itself, but this is not my point here]
}
static_assert(!LikeA<const A>);
int main() {
A a1;
use(a1); //clear, as LikeA<A> holds
const A a2;
use(a2); //not obvious, as LikeA<const A> does not hold
}
I looked into the definitions on cppreference
, and I could not really explain this behaviour, I expected it to be illegal, although intuitively, this really is what I want.
Now on to my real situation: I have a holder class AHolder
that returns const A&
as one of its methods, and I want a fitting concept for this holder class that also applies to any other holder holding anything that satisfies LikeA
, so I tried:
class AHolder {
public:
const A& getA() {return _a;}
private:
const A _a;
};
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> LikeA;
};
static_assert(LikeAHolder<AHolder>); //fails
This fails, since const A&
simply doesn't satisfy LikeA
, so i would love to adjust this to
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> const LikeA; //invalid syntax
};
static_assert(LikeAHolder<AHolder>);
in similar spirit of the example with the use
method also accepting const A
.
Is there such a syntax to require the return type of t.getA
to satisfy LikeA
whilst considering that the return type will be const
?
Additionally, how exactly are concepts checked in the use(const LikeA auto &a)
method such that it behaves like explained?
(My first question is the more important one for me)
Some possible solutions that I have considered:
const
-correctness, since _a
would also have to be non-const
and a user could just change the private attribute of AHolder
. This is no option for me.LikeA
and LikeConstA
. The return type then could be LikeConstA
only requiring the const
methods. This should work, but feels really clumsy and really not how concepts should be used, also this introduces more concepts that necessary to an end-user, who has to bother with them, etc.LikeA
, check whether the templated type T
is constant (via std::is_const
), and if so, don't require the non-const
-methods. This works in above example, but has the undesired effect that we now simply haveclass B {
public:
void print() const {/* ... */}
}
static_assert(LikeA<const B>);
(for an already adapted LikeA
, of course), which also just feels wrong.
LikeA
, use std::remove_reference
and std::const_cast
to cast away references / constness, then check for the required functions. First, i don't know if this will always work for more complicated types, but even then, this now has the undesired effect thatstatic_assert(LikeA<const A>);
will be true, breaking (or at least bending) the semantics of the concept.
To summary and ensure you don't get me wrong, I would like to have a way that
const
-correctnessA
and LikeA
etc.LikeA<const A>
to be trueIdeally, there would just be a feature working like the already-mentioned
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> const LikeA; //invalid syntax
};
{[](const LikeA auto&){}( t.getA() )}
Note that a non-const&
returning getA
will pass this.
I made a lambda that does a concept check, then ensured t.getA()
passes it.
void use (const LikeA auto &a)
This is shorthand for
template<LikeA A>
void use (const A&a)
and when called with a T const
, it deduces A=T
not A=T const
. Why? Because it is "more correct" abstractly. Concretely, there are a pile of rules for how template argument type deduction works.