pstore2
Classes
inherit_const.hpp File Reference

A utility template intended to simplify the writing of the const- and non-const variants of member functions. More...

#include <type_traits>
Include dependency graph for inherit_const.hpp:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  pstore::inherit_const< T, R, RC >
 Provides a member typedef inherit_const::type, which is defined as R const if T is a const type and R if T is non-const. More...
 

Detailed Description

A utility template intended to simplify the writing of the const- and non-const variants of member functions.

It's not uncommon for a class to supply const- and non-const variants of the same member function where the constness of the return type changes according to the constness of the function itself. For example, std::vector<> has:

reference operator[] (size_type pos);
const_reference operator[] (size_type pos) const;

Often the implementation of these two functions is identical: the constness is the only thing changing. This encourages us to want to share the implementation because duplicating code is almost always to be avoided. Writers typically adopt one of three solutions:

  1. Just duplicate the code and move on. For trivial one liners this is a perfectly good solution. For anything more complex however, there's a risk that future changes will modify the implementations and that they'll begin to diverge with time, which can result in new bugs being introduced.
    reference operator[] (size_type pos) {
       return base_ + pos;
    }
    const_reference operator[] (size_type pos) const {
       return base_ + pos;
    }
    
  2. Write one of the functions in terms of the other and use const_cast<> to silence the compiler errors. An attractive options since duplication is avoided completely. However, using const_cast<> is potentially dangerous because by using it the code is really lying to the compiler: the underlying data may be truly read-only even if the types no longer are. This can be a new source of bugs.
    reference operator[] (size_type pos) {
       auto const * cthis = this;
       return const_cast<reference> cthis->operator[] (pos);
    }
    const_reference operator[] (size_type pos) const {
       return base_ + pos;
    }
    
  3. Use macros to past the repeated implementation into the code. Happily, for most writers of C++, this isn't really considered a viable option.
    #define OPERATOR_INDEX(p)  (base_ + (p))
    reference operator[] (size_type pos) {
       return OPERATOR_INDEX(pos);
    }
    const_reference operator[] (size_type pos) const {
       return OPERATOR_INDEX(pos);
    }
    #undef OPERATOR_INDEX
    

The utility here is intended to resolve some of these concerns. For true one-liner member functions it is very likely overkill, but can be useful for implementations that are just fractionally more complex. In the example below I've added an assertion to the shared implementation.

class vector {
public:
    ...
    reference operator[] (size_type pos) {
        return index_impl (*this, pos);
    }
    const_reference operator[] (size_type pos) const {
        return index_impl (*this, pos);
    }
    ...
private:
    template <typename Vector, typename ResultType = typename inherit_const<Vector,
        reference>::type>
    static ResultType index_impl (Vector & v, size_type pos) {
        PSTORE_ASSERT (pos < v.size ());
        return v.base_ + pos;
    }
};