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; }
};
A non-template class can have template member functions, if required. Notice the syntax. Unlike a member function for a template class, a template member function is just like a free template function but scoped to its containing class.
Member functions can be function templates in several contexts. All functions of class templates are generic but are not referred to as member templates or member function templates. If these member functions take their own template arguments, they are considered to be member function templates.
Function templates. Function templates are special functions that can operate with generic types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type. In C++ this can be achieved using template parameters.
A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With