Reputation: 19240
Templates make most things about a function signature parameterizable apart from the function name itself. But is it also possible to parameterize the constness of a member function?
Trivial, minimalist, non-templated example:
struct Foo {
Foo * self() { return this; }
Foo const * self() const { return this; }
};
vs straw-man templated hypothetical:
struct Foo {
template<typename T> T self() std::constness_of(T) { return this; }
};
Upvotes: 12
Views: 372
Reputation: 145239
In a comment on another answer you clarified that
” the goal is to customize constness of the function at will without requiring duplicate code where it's otherwise not needed
The following is one possibility, expressing both const
and non-const
versions of a member function in terms of a templated static
member function.
For the more general case one needs to forward arguments.
Two alternatives are to express the const
member function in terms of the non-const
member function, or vice versa. But as I recall that involves a bit of ugly casting. Or some ugliness, not sure (sorry, I'm now sitting on a very limited internet connection).
#include <string>
//--------------------------------------- Machinery:
template< class Guide, class Result >
struct With_const_like_t_
{
using T = Result;
};
template< class Guide, class Result >
struct With_const_like_t_<Guide const, Result>
{
using T = Result const;
};
template< class Guide, class Result >
using With_const_like_ = typename With_const_like_t_<Guide, Result>::T;
//--------------------------------------- Example usage:
class Bork
{
private:
std::string s_ = "42";
template< class This_class >
static auto foo_impl( This_class& o )
-> With_const_like_<This_class, std::string>&
{ return o.s_; }
public:
auto foo()
-> decltype( foo_impl( *this ) )
{ return foo_impl( *this ); }
auto foo() const
-> decltype( foo_impl( *this ) )
{ return foo_impl( *this ); }
};
#include <iostream>
#include <typeinfo>
using namespace std;
auto main()
-> int
{
Bork v;
Bork const c;
v.foo() = "Hi there!";
#ifdef TEST
c.foo() = "This assignment to `const` won't compile.";
#endif
cout << v.foo() << endl;
}
Upvotes: 1
Reputation: 302842
But is it also possible to parameterize the constness of a member function?
No, you cannot. You don't have access in the function signature to the implicit object to which this
points, so you can't dispatch on it or template on it in any way. cv-qualifiers on member functions have to be spelled out.
For more complicated member functions, you could have one invoke the other (typically the non-const
invoking the const
one to avoid UB) to avoid some code duplication.
Or you could always write a non-member friend
:
struct Foo {
template <class T,
std::enable_if_t<std::is_base_of<Foo, std::decay_t<T>>::value>* = nullptr
>
friend T* self(T& x) { return &x; }
};
We need the SFINAE to ensure that self()
isn't found for unexpected types like Wrapper<Foo>
. Note that this is quite a big longer than your original code, so really only makes sense in the context of having complicated logic.
Would sure be amusing if UFCS was adopted and now we all write our const
/non-const
overloads via non-member friend
s that we still invoke as if they were members.
Upvotes: 7
Reputation: 18051
No, but the work-around is straight forward, and maybe more readable, the intention is clear:
struct Foo {
template<typename T>
std::enable_if_t<std::is_const<T>::value,T> self() const { return this; }
template<typename T>
std::enable_if_t<!std::is_const<T>::value,T> self() { return this; }
};
Upvotes: -1