Reputation: 119
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).
Upvotes: 2
Views: 138
Reputation: 218138
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)];
}
Upvotes: 3