INTRO
I am writing a class stalker<Obj>
that holds inside a variable of type Obj
. I want that stalker<Obj>
to pretend that it is almost the same as Obj
variable (from the user's perspective). However, we hardly know anything about Obj
.
So, I try to achieve this by overloading all possible operators (except for literal ones) and I also overload cast operator for the other unexpected behavior. This creates a lot of code:
template <typename Obj>
class stalker {
Obj obj;
public:
operator Obj&() {
return obj;
}
template <typename Arg>
decltype(auto) operator[](Arg&& arg) {
return obj[std::forward<Arg>(arg)];
}
// and other 20+ overloads...
};
The first problem is that Obj::operator[]
can be returning void
. I do not want to duplicate my code. Here is how i solved it:
EDIT: there is no need for this (see the discussion below)
// useless invention
template <typename Arg>
decltype(auto) operator[](Arg&& arg) {
if constexpr (std::is_same_v<void, decltype(std::declval<Obj&>()[std::forward<Arg>(arg)])>) {
obj[std::forward<Arg>(arg)];
} else {
return obj[std::forward<Arg>(arg)];
}
}
This code already looks heavy (do you know a simpler way?).
PROBLEM
stalker<Obj>
can be tagged as const
or it has a const
type: stalker<const Obj>
. Then we need const
versions of all our operators.
How can we templately overload a void
/non-void
and const
/non-const
operators in one declaration inside a class?
Or is there a better way to achieve stalker<Obj>
behaviour identical to Obj
?
APPROACHES
CPP Reference has: non-duplicate code of both const & non-const versions (help me to understand this).
Another website provides: https://www.cppstories.com/2020/11/share-code-const-nonconst.html/. However, mutable
qualifier or const_cast
are not a solution. I hardly understand the part with templates but it seems that in this way we are not pretending as Obj
anymore (we need to pass obj
in some function).
The first problem is that Obj::operator[] can be returning void.
It is not a problem, returning void
is valid.
template <typename Arg>
decltype(auto) operator[](Arg&& arg) { return obj[std::forward<Arg>(arg)]; }
is ok.
How can we templately overload a void/non-void and const/non-const operators in one declaration inside a class?
As seen above, void/non-void is no problematic. There are still const/volatile and reference cartesion product overloads though.
Before C++23, you have to write all of them.
Since C++23, there is "deducing this
" which allows to write just
template <typename Self, typename Arg>
decltype(auto) foo(this Self&& self, Arg&& arg) {
return std::forward<Self>(self).obj[std::forward<Arg>(arg)];
}
You might SFINAE your method and use same noexcept
to mimic more the type:
template <typename Self, typename Arg>
auto foo(this Self&& self, Arg&& arg)
noexcept(noexcept(std::forward<Self>(self).obj[std::forward<Arg>(arg)]))
-> decltype(std::forward<Self>(self).obj[std::forward<Arg>(arg)])
{
return std::forward<Self>(self).obj[std::forward<Arg>(arg)];
}