Elegant Solution to Duplicate, Const and Non-Const, Getters

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 constness of member functions cannot be deduced. However, using forwarding references allows deducing the costness 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 constness 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:

  1. const overloading is part of the C++ specification, so no errors
  2. 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



Leave a reply



Submit