Elegant solution to duplicate, const and non-const, getters?
I recall from one of the Effective C++ books that the way to do it is to implement the non-const version by casting away the const from the other function.
It's not particularly pretty, but it is safe. Since the member function calling it is non-const, the object itself is non-const, and casting away the const is allowed.
class Foo
{
public:
const int& get() const
{
//non-trivial work
return foo;
}
int& get()
{
return const_cast<int&>(const_cast<const Foo*>(this)->get());
}
};
How do I remove code duplication between similar const and non-const member functions?
Yes, it is possible to avoid the code duplication. You need to use the const member function to have the logic and have the non-const member function call the const member function and re-cast the return value to a non-const reference (or pointer if the functions returns a pointer):
class X
{
std::vector<Z> vecZ;
public:
const Z& z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}
Z& z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
}
#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.z(index) );
}
#endif
};
NOTE: It is important that you do NOT put the logic in the non-const function and have the const-function call the non-const function -- it may result in undefined behavior. The reason is that a constant class instance gets cast as a non-constant instance. The non-const member function may accidentally modify the class, which the C++ standard states will result in undefined behavior.
Avoid literally duplicating code for const and non-const with auto keyword?
Ok, so after a bit of tinkering I came up with the following two solutions that allow you to keep the auto return type and only implement the getter once. It uses the opposite cast of what Meyer's does.
C++ 11/14
This version simply returns both versions in the implemented function, either with cbegin()
or if you don't have that for your type this should work as a replacement for cbegin()
: return static_cast<const A&>(*this).examples.begin();
Basically cast to constant and use the normal begin()
function to obtain the constant one.
// Return both, and grab the required one
struct A
{
private:
// This function does the actual getter work, hiding the template details
// from the public interface, and allowing the use of auto as a return type
auto get_do_work()
{
// Your getter logic etc.
// ...
// ...
// Return both versions, but have the other functions extract the required one
return std::make_pair(examples.begin(), examples.cbegin());
}
public:
std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };
// You'll get a regular iterator from the .first
auto get()
{
return get_do_work().first;
}
// This will get a const iterator
auto get() const
{
// Force using the non-const to get a const version here
// Basically the inverse of Meyer's casting. Then just get
// the const version of the type and return it
return const_cast<A&>(*this).get_do_work().second;
}
};
C++ 17 - Alternative with if constexpr
This one should be better since it only returns one value and it is known at compile time which value is obtained, so auto
will know what to do. Otherwise the get()
functions work mostly the same.
// With if constexpr
struct A
{
private:
// This function does the actual getter work, hiding the template details
// from the public interface, and allowing the use of auto as a return type
template<bool asConst>
auto get_do_work()
{
// Your getter logic etc.
// ...
// ...
if constexpr (asConst)
{
return examples.cbegin();
// Alternatively
// return static_cast<const A&>(*this).examples.begin();
}
else
{
return examples.begin();
}
}
public:
std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };
// Nothing special here, you'll get a regular iterator
auto get()
{
return get_do_work<false>();
}
// This will get a const iterator
auto get() const
{
// Force using the non-const to get a const version here
// Basically the inverse of Meyer's casting, except you get a
// const_iterator as a result, so no logical difference to users
return const_cast<A&>(*this).get_do_work<true>();
}
};
This may or may not work for your custom types, but it worked for me, and it solves the need for code duplication, although it uses a helper function. But in turn the actual getters become one-liners, so that should be reasonable.
The following main function was used to test both solutions, and worked as expected:
int main()
{
const A a;
*a.get() += 1; // This is an error since it returns const_iterator
A b;
*b.get() += 1; // This works fine
std::cout << *a.get() << "\n";
std::cout << *b.get() << "\n";
return 0;
}
macro solution for duplicate, const and non-const, getters?
If a function doesn't return a value (reference or pointer), do you need const overloads in the first place? It seems that the only reason to have two is to get a const or non-const reference out of the class. Otherwise the const version alone should be enough.
As to making the idiom sweeter, how about a few template helpers instead to do type deduction for you:
template <class T>
const T& add_const(T& t) { return t; }
template <class T>
T& remove_const(const T& t) { return const_cast<T&>(t); }
class Foo
{
int foo;
public:
const int& get() const
{
//non-trivial work
return foo;
}
int& get()
{
return remove_const(add_const(*this).get());
}
};
The benefit is greater if the type names are longer.
How to avoid code duplicate const and non-const collection processing with lambda
You can factor out the function as a static template one and use it inside both. We can use the template to generate both of these functions:
struct Collection {
// ...
template<typename Processor>
void ProcessCollection(Processor& processor) {
ProcessCollectionImpl(*this, processor);
}
template<typename Processor>
void ProcessCollection(Processor& processor) const {
ProcessCollectionImpl(*this, processor);
}
template<typename T, typename Processor>
static void ProcessCollectionImpl(T& self, Processor& processor) {
for( int idx = -1 ; ++idx < self.m_LocalLimit ; )
{
if ( self.m_Data[ idx ] )
{
processor( self.m_Data[idx] );
}
}
const int overflowSize = self.OverflowSize();
for( int idx = -1 ; ++idx < overflowSize ; )
{
processor( (*self.m_Overflow)[ idx ] );
}
}
};
The T&
will deduce Collection&
or Collection const&
depending on the constness of *this
Pitfalls of implementing a reference getter with a const_cast of a const-ref getter
One clean way is to delegate to a private template function which can deduce the constness of this
:
#include <cstdint>
template<typename T>
class Vector {
public:
T& operator[](std::size_t i) {
return impl_indirection(this, i);
}
const T& operator[](std::size_t i) const {
return impl_indirection(this, i);
}
private:
template<class MaybeConstVector>
static decltype(auto) impl_indirection(MaybeConstVector* pv, std::size_t index)
{
return pv->data[index];
}
T* data;
};
Avoiding const/non-const duplication of method accepting a lambda
The const
ness of member functions cannot be deduced. However, using forwarding references allows deducing the cost
ness of function arguments. Thus, you can just delegate to a function taking the object type as argument and forwarding all other arguments. This way the duplicated code becomes trivial. If the function needs access to private
or protected
members you’d just make it a (probably private
) static
member:
template <typename T, bool PM = true>
class Frame {
template <typename F1, typename F2>
static void iterateTogether(F1&& f1, F2&& f2) {
// actual implementation goes here
}
public:
template <typename F2>
void iterateTogether(F2&& other){
iterateTogether(*this, std::forward<F2>(other));
}
// ...
};
The implementation used above also allows different const
ness of the parameter. If you want to constrain the parameter to really just be a specialization of Frame
you’d need to constrain the function which is, however, easily done.
My personal preference is having only trivial members anyway and delegate anything non-trivial to generic algorithms. My preference is that classes just maintain their invariants and interesting uses are implemented via algorithms.
how to use non-const function in a const function?
The solution is to have two locate
functions: one is const
and the other isn't. You'll use a const_cast
in the non-const version, and the resulting code is legal because the entry point for SegmentNode
was non-const.
const SegmentNode& SegmentNode::locate(const Segment& s) const{
// as you have it already
return* this;
}
SegmentNode& SegmentNode::locate(const Segment& s){
return const_cast<SegmentNode&>(static_cast<const SegmentNode*>(this)->locate(s));
}
Demo
Obligatory edit: There should be two versions of locate
anyway that account for const
-ness. The solution I have above is just a way to avoid duplicating code, ugly as it is.
Doesn't compiler give an ambiguous error for const and not-const functions
This is const overloading, which is a thing in C++.
In this case, the compiler determines the struct does NOT have an overloaded function return type, (which is disallowed due to ambiguity of course) but rather an overloaded function with different “constness;” a term in one of the other answers which makes sense here.
As for getting no compiler errors:
- const overloading is part of the C++ specification, so no errors
- At compile-time, the compiler “sees” that the struct is returning to a non-const environment, so uses the non-const function, causing a normal operation to happen
Hope that helps!
Related Topics
How to Output Coloured Text to a Linux Terminal
What Open Source C++ Static Analysis Tools Are Available
Is C++ Context-Free or Context-Sensitive
Why Vector≪Bool≫::Reference Doesn't Return Reference to Bool
Why Does Const Imply Internal Linkage in C++, When It Doesn't in C
Unresolved External Symbol in Object Files
What Constitutes a Valid State For a "Moved From" Object in C++11
How to Parse Space-Separated Floats in C++ Quickly
What How to Use Instead of the Arrow Operator, '-≫'
Uses For Multiple Levels of Pointer Dereferences
Return Statement VS Exit() in Main()
Writing Your Own Stl Container
C++ Dynamic Shared Library on Linux
Efficiently Load a Large Mat into Memory in Opencv
Using Char* as a Key in Std::Map
Deleting Elements from Std::Set While Iterating
When Were the 'And' and 'Or' Alternative Tokens Introduced in C++