Table of Contents

During the development of a container-like type, I run into the problem of how to share code between a const and non-const member functions. In this article, I’d like to explain what are the issues and possible solutions. We can even go on a bleeding edge and apply some C++20 features. Which technique is most friendly?

Have a look.

The Problem

The container I’m working on is more complicated, but here’s a simplified version to illustrate the problem:

struct Part {
    std::string _name;
    bool _isAvailable { false };
};

class PartsStore {
public:
    PartsStore(const std::map<int, Part>& parts) : _parts(parts) { }
    
    bool Contains(std::string_view name) {
        return FindByNameImpl(name) != nullptr;
    }
    
    void SetAvailability(std::string_view name, bool isAvailable) {
        auto pPart = FindByNameImpl(name);
        if (pPart)
            pPart->_isAvailable = isAvailable;
    }
    
private:    
    Part* FindByNameImpl(std::string_view name) {
        auto it = std::find_if(begin(_parts), end(_parts), [&name](const auto& entry) {
            return entry.second._name == name;
        });
        if (it != _parts.end())
            return &it->second;
        return nullptr;
    }
    
    std::map<int, Part> _parts;    
};

Code available here @Wandbox

As you can see above, we have a container of Parts. This class type wraps a standard std::map and adds some additional interface.

The core issue is that there are member functions like Contains or SetAvailability. Right now they are all non-const and then call some internal helper function that does the job of finding a Part by name.

FindByNameImpl is maybe not super advanced, but we can assume that such a function can contain some extra logic that we’d like to share across other callers.

What’s the issue then? PartsStore seems to do the job.

The Contains function is non-const… but it should (not to mention noexcept, but we can save that for some other discussion). It doesn’t modify the internal state, so we really have to apply some const correctness here.

See more reasons about applying const in my separate article: Bartek’s coding blog: Please declare your variables as const

But then, the code won’t compile as this function calls non-const code. Also, we cannot just mark FindByNameImpl with const as it’s called by non-const function SetAvailability (this won’t compile without explicit casts). So they are all “connected”…

That’s why it would be best to find an approach and share the code in FindByNameImpl efficiently between those two functions.

Sharing Code in Const and Non-Const Functions

I did some research and found several ways of how we can address this “sharing” problem.

Let’s start with the most obvious one:

Code Duplication

While this simple solution is probably not the best approach it allows us to see where const has to be applied:

Part* FindByNameImpl(std::string_view name) {
	auto it = std::find_if(begin(_parts), end(_parts), [&name](const auto& entry) {
		return entry.second._name == name;
	});
	if (it != _parts.end())
		return &it->second;
	return nullptr;
}

const Part* FindByNameImpl(std::string_view name) const {
	auto it = std::find_if(begin(_parts), end(_parts), [&name](const auto& entry) {
		return entry.second._name == name;
	});
	if (it != _parts.end())
		return &it->second;
	return nullptr;
}

See code @Wandbox

The Mutable Keyword

We had code duplication in the previous point, so why not take other direction and use a handy brute force approach and apply mutable to our data member?

Just to remind:

mutable - permits modification of the class member declared mutable even if the containing object is declared const.

But… this is an even worse idea than a direct code duplication!

See in the C++ Core Guidelines: ES 50

Sometimes, “cast away const” is to allow the updating of some transient information of an otherwise immutable object. Examples are caching, memoization, and precomputation. Such examples are often handled as well or better using mutable or an indirection than with a const_cast.

In other words, sometimes it might be handy to apply mutable but only to additional data members that “enhances” operations on the core state of our class. For example, we can have some extra caching system.

In our case std::map<int, Part> _parts; is “core” state, so it’s definitely not the best idea to alter it.

const_cast From non-const Function

Finally, we can look at some more concrete solution.

Let’s reach out to Scott Meyers and in his Effective C++ 3rd Edition. On page 23, Item 3 (on using const) we can read that a non const function can safely call const one. To achieve this, we can leverage <const_cast>. In our case, this boils down to the following code:

class PartsStore {
    
public:
    PartsStore(const std::map<int, Part>& parts) : _parts(parts) { }
    
    bool Contains(std::string_view name) const {
        return FindByNameImpl(name) != nullptr;
    }
    
    void SetAvailability(std::string_view name, bool isAvailable) {
        auto pPart = const_cast<Part*>(FindByNameImpl(name));
        if (pPart)
            pPart->_isAvailable = isAvailable;
    }
    
private:       
    const Part* FindByNameImpl(std::string_view name) const {
        // impl...
    }
    
    std::map<int, Part> _parts;    
};

See code @Wandbox

In this case, I removed const from the pointer that is returned from FindByNameImpl which is now a constant function.

There might be many variations on that approach, especially when you want to avoid recursive calls…

As Scott Meyers explains, calling functions this way is safe. Since a const function promises not to modify the internal state of the object, then we’re not breaking it. On the other hand, the reverse is not possible - i.e. calling non-const function from a const one. This time we break a promise of not altering the state, so this can generate Undefined Behavior (UB).

This technique is very promising, but let’s see another one, that doesn’t require casts at all.

Templates To The Rescue

In a basic form, we can use templates to generate necessary code, depending on the caller needs. In other words, the compiler will generate two versions of the function for us.

For the implementation I created a static function template. The function is parametrised over the container type:

template <typename T>
static auto FindByNameImpl(std::string_view name, T& container) {
    auto it = std::find_if(begin(container), end(container), [&name](const auto& entry) {
        return entry.second._name == name;
    });

    return it != end(container) ? &it->second : nullptr;
}

See code @Wandbox

This is nice! The compiler can enforce additional checks and don’t need any casts. One disadvantage is that we have a function template, so possible we need to put that in a header file… or define it a free function in a cpp file.

Enhancing with C++20

We can even experiment with some C++20 features and restrict out function template to work only with the map container:

template <typename T> requires std::is_same_v<std::map<int, Part>, std::remove_cv_t<T>>
static auto FindByNameImpl(std::string_view name, T& container)

See code @Wandbox

Summary

In the article, you’ve seen four techniques (plus one enhancement) that allows you to share code between const and non-const member functions. While the first two patterns are probably not the best idea: direct code duplication and the application of the mutable keyword - they server the illustrative purpose. But the last two techniques are more practical and safer.

For my use case, I think I’ll stick with a template solution as it doesn’t need any casts and compiler can check const correctness better.

What do you think about those solutions? Maybe there are some other approaches?

References

Join the discussion @reddit/r/cpp.