1

I'm reading through Scott Meyers effective C++ book and he recommends for operators making both const and non-const versions, where the non-const just calls the const version will some type stripping to reduce code repetition.

His example is something like this:

const char& CallConstTest::operator[](size_t index) const
{
    //Some safety code
    return m_buffer[index];
}

char& CallConstTest::operator[](size_t index)
{
    return const_cast<char&>(static_cast<const CallConstTest&>(*this)[index]);
}

Now I agree with doing this but I really don't like those casts for readability reasons, so added some template functions that work to try and increase readability as shown below:

template<typename baseType>
const baseType& AddConst(baseType& base){
    return static_cast<const baseType&>(base);
}

template<typename baseType>
baseType& RemoveConst(const baseType& base) {
    return const_cast<baseType&>(base);
}

char& CallConstTest::operator[](size_t index)
{
    return  RemoveConst(AddConst(*this)[index]);
}

But then thought to myself, everyone will tell me to use a standard library to do this so it's uniform with any codebases. I found std::add_const and std::remove_const but the add explicitely says "(unless T is a function, a reference, or already has this cv-qualifier)" so that's a no go, and I cannot get std::remove_const to be happy, probably for similar reasons.

Questions

  • Is there a commonly used function that everyone already uses for this
  • If you argue that const_cast<type&>(static_cast<const type&> is actually the correct answer, please explain why
Natio2
  • 235
  • 1
  • 9
  • 1
    See https://en.cppreference.com/w/cpp/language/member_functions#Explicit_object_parameter – Passer By Jul 17 '22 at 03:35
  • @PasserBy That is super interesting, although it is "(since C++23)" noted on the feature I would use, so not really universal yet. – Natio2 Jul 17 '22 at 04:00
  • is there an actual use case here? – Captain Giraffe Jul 17 '22 at 04:02
  • @CaptainGiraffe There's a use case in my code above. The idea is to not rewrite the code in the const operator in the non-const operator, achieving this by just calling one from the other. It's used extensively, see this great article which gives examples in operator++ (not const but same idea) https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading – Natio2 Jul 17 '22 at 04:05
  • 1
    I can't imagine Scott Meyers promoting `const_cast`. const_cast usually only has a role when communicating with legacy API's. const is a contract NOT to change a variable, with const_cast you are (likely) to break that contract. – Pepijn Kramer Jul 17 '22 at 04:07
  • @PepijnKramer It's specifically for operator overriding where it is deemed safe. He does make a comment about it when recommending it. – Natio2 Jul 17 '22 at 04:08
  • 1
    The reason why a new solution was invented was because the old ones are all bad. `const_cast` is not pretty, but neither is code duplication. There is no commonly used function. – Passer By Jul 17 '22 at 04:17
  • 1
    In the end it is like so many things a trade-off between readability, maintainability (keep rules simple for your dev team : don't use const_cast) and code duplication. And for me sometimes that just means accepting a bit of code duplication (with unit tests!!!). – Pepijn Kramer Jul 17 '22 at 04:47
  • I have a problem with all solutions to "don't repeat yourself" that creates *more* code than the original. One other "solution" is to just change the `index` parameter in the operators to `x` and `y` respectively. And now the code is different! Or you could just come to terms with not spending time trying to improve one-line functions. "Good enough" is sometimes better than "perfect". – BoP Jul 17 '22 at 10:10
  • @BoP maybe in a single class, but overall I disagree. Like with the template function I wrote above I have written the line of code that can now be used for any class to avoid rewriting the non-const version of the operator[], the problem is now just solved in the code base an no longer has to be thought about again. This is a pretty common scenario in C++, this is the reason the compiler does things like automatically makes the copy constructors; yes you could always write them, but you'd be writing a tonne of trivial ones that have no need. – Natio2 Jul 17 '22 at 10:52
  • @Bop it should also be noted the comment "//Some safety code" is the place holder for many more lines of code that needs to be called in both and lastly the compiler already knows these are different functions and are already overloaded from the "const" keyword, that isn't the issue. – Natio2 Jul 17 '22 at 10:54

1 Answers1

1

The standard library facilities you named are type traits, but there is std::as_const for AddConst. The other direction has no common name (perhaps because it is unsafe in general).

However, you don’t need the one it could implement if you can define the const version in terms of the other:

const char& CallConstTest::operator[](size_t index) const
{
    return const_cast<CallConstTest&>(*this)[index];
}

char& CallConstTest::operator[](size_t index)
{
    //Some safety code
    return m_buffer[index];
}

The one cast is necessary to select the other overload, but then the return value can be implicitly converted (as it could be in AddConst).

Meyers is right that this sort of const_cast is reasonably idiomatic, although one could certainly find programmers (perhaps ones who had been burned by wanton casting before) who would prefer duplication of at least a small function body.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Thanks for the detailed answer, this would work. Meyers does say specifically to call the const one from the non-const, as you know this is safe, if you do it the other way you lose this guarantee, as the non-const could mess with the data. I think if you don't follow this you start getting into the argument of if you should do this. – Natio2 Jul 18 '22 at 05:45
  • @Natio2: Knowing it’s safe to apply the “dangerous” `const_cast` to the result of the const version is equivalent to knowing that it’s safe to call the non-const version so long as the result has `const` immediately reapplied. If you needed to do more in the non-const version, it would of course work only in the direction of using the const version as a subroutine, but the usual recommendation is to avoid such subtle distinctions anyway. – Davis Herring Jul 18 '22 at 12:47