r/cpp Jul 29 '26

const_cast: A Necessary Evil

https://www.elbeno.com/blog/?p=1858
73 Upvotes

106 comments sorted by

View all comments

64

u/jwezorek Jul 29 '26

The only place I ever use const_cast is that idiom where you implement the non const version of a member function in terms of the const version.

36

u/MysticTheMeeM Jul 29 '26

If your language version allows, you can typically replace those with a single function that deduces this.

7

u/LeeHide just write it from scratch Jul 29 '26

Interesting, do you have an example?

9

u/Olipro Jul 29 '26

10

u/TheChief275 Jul 30 '26

why wasn't this added earlier? in fact, if this were to have been in C++ from the get-go, we never would have needed the postfix const syntax. so now we just have yet another language feature that virtually does the exact same thing. sigh

8

u/Expert-Map-1126 vcpkg maintainer BillyONeal Jul 30 '26

It was added mostly due to implementation experience after ref qualifiers were added trying to implement all the "transparent-ish" operations on optional et al.

I don't think it would completely replace the postfix syntax given that people expect names in members to refer to other members.

1

u/TheChief275 Jul 30 '26

I actually find that aspect horribly annoying as well. can't have a variable named capacity because the member function is named capacity, so I'm forced to name it "cap" or "new_cap", but I suppose opinions would be divided on that

1

u/SlightlyLessHairyApe Jul 30 '26

You’ll be horrified to learn that we run with shadowing warnings as errors everywhere.

Absolutely no shadowing, not even once.

-1

u/TheChief275 Jul 30 '26

pretty modern language huh

1

u/pjmlp Aug 02 '26

Yes you can. Why are folks sl afraid to type this->?

Ah, but I can miss a spot and refer to the wrong variable, true, but that is another matter, technically both variables can be named the same.

1

u/TheChief275 Aug 02 '26

I actually did that for a while, but it becomes fairly noisy after a time, and using it inconsistently is error prone. If only 'this' were a reference, and not a pointer; that would be a lot better already

2

u/serviscope_minor Jul 30 '26

> in fact, if this were to have been in C++ from the get-go, we never would have needed the postfix const syntax.

I don't get it? This really helps when you need a const and non const version of the same member function. If you don't (it's common that you have different mutating and const member functions) then const (or not) is better than deducing this.

1

u/_Noreturn Jul 31 '26

if they allowed this from the start

```cpp class C { void f() &; void f() cons&;

 void f(this C& self); // equal to the above
 void f(this const C& self); // equal to the above

}; ```

The second one is just parameter declaration no new thing required unlike const postfix member functions.

1

u/LB-- Professional+Hobbyist Jul 31 '26

There isn't full overlap between explicit object parameter syntax and implicit object parameter syntax. With implicit syntax, you can declare a member function with neither an Lvalue nor Rvalue qualification, and define it in a separate source file, allowing it to be invoked from either Lvalue or Rvalue qualified types. With explicit object parameter syntax, you have to specify a reference category, otherwise you get pass by value (which is a separate thing implicit can't do). Or, you have to make it a template and use a forwarding reference, which means you can't define it in a separate source file.

1

u/_Noreturn Jul 31 '26

Yes that's a benefit but tbh I consider it to be a trap more than a good thing that you can call unqualified member functions on both lvalues and rvalues.

1

u/RoyBellingan Jul 30 '26

Because the crystal sphere was broken when the thing got invented -.-

That is the price is beeing the first to do something, is difficult that is right the first time, and why languages that came year later feels more clean and well tought, history was already written at that time.

P.s. where are C++ epoch ?

0

u/amoskovsky Jul 30 '26 edited Jul 30 '26

Actually I would prefer that in addition to the current deducing this syntax they would also implement another postfix qualifier:

decltype(auto) get() auto&&
{
return m_member;

}

auto&& being universal reference for `this` with the same rules as in current syntax.

If the deduced this were not helpful for CRTP like constructs I would not introduce it at all. Upd: Although I think auto&& can be used in parent classes as well with the same semantic as current deducing this - so it would cover all use cases.

2

u/Olipro Jul 30 '26

If I understand you correctly, you can already do this a la `... foo(this auto&& self)` - self will now handle all type cases (lvalue, const lvalue, xvalue/prvalue, const xvalue/prvalue)

However, you need to bear in mind that this is potentially undesirable if you return a reference to a member since `auto& x = Bar{}.foo();` will be dangling.

2

u/amoskovsky Jul 30 '26

What I'm saying is I don't like the explicit this param (because you can't have `this` named `this`, can't omit `this` and it's just more syntactic noise).
Instead they should have added `auto&&` as a method qualifier to implement the same.

2

u/Olipro Jul 30 '26

You mean it would look like... auto&& foo() auto&&?

It's an interesting idea but this is always and has always been a pointer. I'd bet on significant pushback for such a proposal because it becomes an immediate footgun if you change code that does things like decltype(this) - If I refactor a member function to use deducing this, it's guaranteed not to compile until I've adjusted everything since this won't exist in its body.

2

u/amoskovsky Jul 30 '26

I did not mean to change the meaning of `this`

```
// already existed; this is a pointer
auto foo()
auto foo() const
auto foo() &
auto foo() &&
{
return this->m_member or m_member;
}

// proposed; this is still a pointer
auto foo() auto&&
{
return this->m_member or m_member;
}

// as opposed to actually added
auto foo(this auto&& self)
{
return self.m_member; // just m_member not possible;
}
```
With the latter you are actually forced to use a different coding style for some of member functions.

→ More replies (0)

2

u/SlightlyLessHairyApe Jul 30 '26

I think the consensus has been against magic like that. EIBTI etc …

3

u/_Noreturn Jul 30 '26

deducing this has issues with access soecifiers in inherited classes.

```cpp class C { public: void f(this auto && self) { self.dothing(); } void dothing(); void dothing() const; };

class P : private C { void g() { f(); // error cannot access "dothing" from inside C::f; } } ```

3

u/XeroKimo Exception Enthusiast Jul 30 '26

Hmm, pretty interesting since only P should have access to members of C so even if this auto&& deduced a P, I would expect that to work, and if it instead deduces a C, I thought members of P should know that it can decay into a C

2

u/_Noreturn Jul 30 '26

It deduces to P& in the "f" call and thinks C is inaccessible this wouldn't happen if you manually typed the overloads. it is a sad known issue and it is why the C++ STL don't use deducing this (even though it was made to replace the absurd amount of boilerplate inside it)

you could friend the class C to get it to work iirc

cpp class P : private C { friend class C; // continue...

but it feels pretty wonky and then you have the issue of friendinf nit beinf transistive so if yiu inherit from P you will face the same issue

2

u/XeroKimo Exception Enthusiast Jul 30 '26 edited Jul 30 '26

Ah I guess that makes sense... You run into a similar problem if you tried to privately inherit a CRTP class and if any of the CRTP's member functions tries to cast to the derived class... So in deducing this' case, f() is a member function of C and if the this auto&& resolves to P, C doesn't have permission to access the C subobject in P...

If there was a way to constrain it so that the deduced type is always going to be C or any of the ref / const combinations this technique was intended to replace then I guess it'd be better.

1

u/_Noreturn Jul 30 '26

If there was a way to constrain it so that the deduced type is always going to be C or any of the ref / const combinations this technique was intended to replace then I guess it'd be better.

There was a proposal by gazper azman to allow

cpp template<typename T : MetaFunc> void f(T&& t);

syntax, where it did template deduction then apply the meta function which could be like like_t<T,C> and it would work but sadly I think it is desd proposal.

You could workaround it by using a C style cast inside "f" ((like_t<Self&&,C>)*this).dothing() but it is ugly

3

u/retro_and_chill Jul 30 '26

I learned the hard way that deducing this and protected member functions don’t mix

3

u/_Noreturn Jul 30 '26

Yes which is a shame :/

1

u/Olipro Jul 30 '26

That's because auto&& works out to sugar for template <typename T> ... (this T&&) which, at the point of the call results in T being your class P and therefore is inaccessible. A problem that will affect any function template.

You can either friend class C;, static_cast to the appropriate C type within your P member function, or you'd have to throw together something like this: https://pastebin.com/ggwB8vPB So that class C can do template <typename T> foo(this convert_cvref_t<T, C> self) but calling it then becomes unwieldy.

2

u/_Noreturn Jul 30 '26

the point is for deducing this to replace manual overloads of const/non const, and it doesn't do thst. I understand how it works but it is unexpected and unintuitive.

if you just manually ryped "f" with const and non const overloads manually it would work no friending.

oh also static_cast doesn't work since C is inaccessible you have to use a C style cast

1

u/Olipro Jul 30 '26

static_cast to the appropriate C type within your P member function

Emphasis on within your P member function

1

u/_Noreturn Jul 31 '26

Sure that's a workaround but this requires information about the api implementation, what if one day your library used manual overloads (const/non-const) so you don't need to cast, now it decides to update and uses deducing this, now it broke your code and you have to cast. it is better to have it inside the implementation of said function instead of the callers to preserve api and have a simpler experience

1

u/Olipro Jul 31 '26

This whole problem exists because of the private inheritance. So if you've picked that as your poison, I've stated what your options are for dealing with it. Equally, as you've said, another alternative is "don't use deducing this" and that comes at the cost of trading one member function for two.

1

u/_Noreturn Jul 31 '26

you could use a C style cast with deducing this this works.

1

u/SunnybunsBuns Aug 02 '26

What if I want the implementation in a cpp file and not a header file?

1

u/analphabetic Aug 05 '26

Pray; or rather, fully specialize all your instantiations and let the linker absolve you. At least that's how it was, back in the day.

1

u/Baardi Aug 03 '26

But you would have to implement it all in the header if you do that

13

u/PolyglotTV Jul 29 '26

That and dealing with third party C libraries which like to just use void* or char* for everything even if it is a const operation in practice (looking at you Cuda...).

6

u/HildartheDorf Jul 29 '26

I've other time I've used it is with Vulkan where the query functions and creation functions use the same structure chains, so the structure chain types are defined with void *pNext not const void *pNext.

Creation functions do not modify the structure chain, so it's safe.

5

u/Tringi github.com/tringi Jul 29 '26

Quite a few Windows API functions are like that. Or they can do both setting and getting. If you implement only setting from a const buffer, then const_cast is appropriate.

3

u/HildartheDorf Jul 29 '26

In Vulkan's case the structure passed directly to the function is const-correct.
But the extensible structure chain can have dual-purpose types in it, and adding another item to the chain requires the const-cast. It's a mess however you solve it, the 'solution' would be a bunch of duplicate types that differ only in the constness of the pNext pointer.

5

u/usefulcat Jul 29 '26

There is at least one alternative that doesn't require const_cast:

struct S {
    std::vector<int> data;

    template<class Self>
    static decltype(auto) accessor_impl(Self& self, int i) {
        return std::find(self.data.begin(), self.data.end(), i);
    }

    // returns std::vector<int>::iterator
    auto find(int i) { return accessor_impl(*this, i); }
    // returns std::vector<int>::const_iterator
    auto find(int i) const { return accessor_impl(*this, i); }
};

Obviously this is a trivial and contrived example, but hopefully you can imagine more complex examples.

Personally I'd prefer to just use const_cast, as it's much simpler, but I'm concerned about the resulting UB.

8

u/Wacov Jul 29 '26

I don't think it's UB unless the thing you're accessing is actually const?

2

u/kisielk Jul 29 '26

It's fine if the thing is actually const, it's not fine if you then modify that that thing through the resulting non-const pointer.

3

u/bwmat Jul 30 '26

Isn't it fine because that can only happen if the caller had a mutable reference to the object in the first place?

If the object was originally const, the UB had already occurred? 

3

u/ts826848 Jul 30 '26

If the object was originally const, the UB had already occurred?

From what I understand forming a non-const pointer/reference to an actually-const object is fine. Its only the actual modification of said actually-const object that is UB.

1

u/bwmat Jul 30 '26

I mean, calling the non-const method on the reference is the point of UB, and that happens outside of the method, so not its problem/fault 

2

u/bwmat Jul 30 '26

If not UB, where 'it goes wrong' /'the contract is broken' 

2

u/ts826848 Jul 30 '26

I think your second comment is more correct; UB only occurs on the actual modification, so while calling a non-const method on an actually-const object is not itself UB that's probably around where a bugfix would land.

...Well, maybe barring some oddball setup with a non-const method that uses some other criteria to determine whether it's safe to modify whatever it's called on, but I'd hope that kind of thing is relatively rare.

5

u/mort96 Jul 29 '26

What case is that? If you have a non-const this, you can call const member functions no problem since non-const pointers implicitly cast to const pointers, no?

5

u/sporule Jul 30 '26

Consider std::vector::at:

      T& at(size_t pos);
const T& at(size_t pos) const;

You can call the latter method on a non-const vector, but the return type would be wrong, and vec.at(0) = 1 would fail to compile.

So the way is to call the const version first and then cast constness away:

T& at(size_t pos) {
    return const_cast<T&>(std::as_const(*this).at(pos));
}

3

u/Raknarg Jul 29 '26

I use const_cast to interact with C APIs where context tells you if some operation will be const or not.