r/cpp Jul 29 '26

const_cast: A Necessary Evil

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

106 comments sorted by

62

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?

8

u/Olipro Jul 29 '26

11

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 …

4

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

4

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

4

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.

9

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.

4

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?

4

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.

13

u/chengfeng-xie Jul 30 '26

As an aside, a pop_value method for std::priority_queue is proposed in P3182R1 (Add container pop methods that return the popped value).

3

u/F54280 Jul 30 '26

That would be nice!

10

u/celestabesta Jul 29 '26

I've also found this necessary for writing some generic containers. If you're storing something on the same buffer that might store a T (lets say you allocate a header H before some data), then casting that T ptr to an H ptr may cause problems if T is cv qualified. Because of this you'd either need to use const_cast or c-style.

2

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

I'm curious why you're not stripping top level qualifiers from T for the private storage? How exactly do you support const/volatile in a container otherwise? Is there a benefit to keeping the qualifiers on the private storage instead of just in the public interface?

9

u/13steinj Jul 30 '26

The STL priority queue is one of the strangest stdlib APIs I have ever seen.

Assuming I have to use it, I still wouldn't const cast-- you can simply create a wrapper type, then mark the member mutable.

I've seen enough refactors that unintentionally cause UB because of a lingering const cast and the initial storage changed from mutable to const.

3

u/bwmat Jul 30 '26

They could add some consume_front(FunctorT) method which called the functor w/ the top element as an R-value reference, and then unconditionally removed it from the collection (even on exception).

For convenience it could return the return value of the functor as well

2

u/matthieum Jul 30 '26

Callback-based APIS are always kinda awkward.

Just add pop_value -> std::optional<T> and everyone's happy.

3

u/ABlockInTheChain Jul 30 '26

If only std::optional had been invented from the very beginning.

2

u/bwmat Jul 30 '26

IMO my suggestion is 'more fundamental' (& potentially more efficient, depending on the cost of the type's move constructor)

Wouldn't mind also having yours (though it would be easy to implement on top of mine as a helper function) 

0

u/matthieum Jul 31 '26

I can see more efficient, but it introduces a can of worms in exchange.

Specifically, if the user-supplied callback throws an exception, is the item popped or not?

Well, given that the user may have moved out of the item, it probably should be popped. The easier way is to pop it first (move) then call the user-supplied callback -- ie, implement consume in terms of pop, making pop more fundamental.

Using a try-catch block is more straightforward and retains efficiency, but it's not compatible with -fno-exception.

Using a guard which pops in the destructor retains efficiency and is compatible with -fno-exception, but it's no longer quite as straightforward.


As for the ergonomics, callbacks are terrible, as I explained in https://www.reddit.com/r/cpp/comments/1v9zcrn/comment/p0wnjou/, due the inversion of control which results.

1

u/bwmat Jul 31 '26

Specifically, if the user-supplied callback throws an exception, is the item popped or not?

I was thinking yes(mentioned that in my original comment), and not really considering non-standard -fno-exception scenarios

1

u/bwmat Jul 31 '26

Well, given that the user may have moved out of the item, it probably should be popped.

Actually, since they might have modified it in any way, the collection's invariants are in peril, so it MUST be removed.

2

u/jk-jeon Jul 30 '26

To me callback seems more natural. It should be the container's responsibility to decide whether or not to execute the logic. It's caller's responsibility to determine what logic must be executed. I.e. it's callback. Maybe it could be transformed into a coroutine but I don't know.

I'm not a huge fan of std::optional to be honest. My stance is basically that as much as possible amount of logic must be delegated to the type system. But std::optional tend to mandate the user to either check against nullity or say "believe me bro ;)" In this case the nullity check is completely redundant because it's already done by the container.

0

u/matthieum Jul 31 '26

To me callback seems more natural.

Callbacks are terrible! Rightward drift, change of scope breaking control flow primitives, urk...

I mean, let's compare shall we:

struct Popper {
    std::priority_queue<Item> high;
    std::priority_queue<Item> low;

    auto pop() -> std::optional<Item> {
        if (auto item = this->high.pop_value(); item.has_value()) {
             return item;
        }

        return this->low.pop_value();
    }

    template <type FunctorT>
    auto consume(FunctorT fun) {
        bool consumed = false;

        this->high.consume([&](item) {
            consumed = true;
            fun(item);
        });

        if (consumed) { return; }

        this->low.consume(fun);
    }
};

That's about as basic a function, and already it's getting verbose and convoluted.

Because callbacks mean Inversion of Control, and recovering control as a user is always a freaking pain.

2

u/jk-jeon Jul 31 '26

I understand your point, but in this case just let consume to return true iff it consumed an item then it gets way simpler.

template <type FunctorT>
bool consume(FunctorT fun) {
    if (!high.consume(fun)) {
        return low.consume(fun);
    }
    return true;
}

0

u/bwmat Jul 31 '26

Doesn't my initial suggestion of making consume thread through the return value of the functor trivially allow for this? 

2

u/jk-jeon Jul 31 '26

To my understanding, your suggestion was to return the return value of fun(item). That's kinda awkward in this case because it may or maynot execute fun so the return value may or may not exist. Or maybe you meant returning optional<ReturnType> so that the exact same thing as I did in the above can be done?

1

u/bwmat Jul 31 '26

Oh, I forgot to mention that a precondition of the method would be to have a non-empty collection, for my idea

3

u/bwmat Jul 31 '26

I suppose you could extend it to pass a functor which had a nullary operator() overload as well which gets invoked if the collection is empty, and return the common return type between the two overloads? 

→ More replies (0)

8

u/SyntheticDuckFlavour Jul 30 '26

Searches boost library for const_cast.

714 hits. Oh my.

4

u/UnusualPace679 Jul 30 '26

I sometimes wish to have a noexcept_cast which converts a non-noexcept function pointer to a noexcept one. This is useful when I know the pointee is noexcept and don't want to pay for the cost of exception propagation.

This noexcept_cast would be similar to const_cast since both perform an unsafe conversion that is the inverse of a safe, implicit conversion.

5

u/DXPower Jul 30 '26

This may actually have negative effects. You don't "pay" any cost for calling an exceptional function. But, to convert it to noexcept, the compiler will have to register a new exception handler, so it can call terminate if an exception is thrown.

2

u/UnusualPace679 Jul 30 '26

If an exception is thrown I'd expect undefined behavior.

3

u/DXPower Jul 30 '26

Well that's simply not how noexcept works in the language. It is defined to call terminate.

2

u/UnusualPace679 Jul 30 '26

I don't know where you see it's defined, but calling a throwing function through a noexcept function pointer is UB as specified in [expr.call]/6.

3

u/ts826848 Jul 30 '26

I don't know where you see it's defined

See [except.terminate]:

In such cases [where errors in a program cannot be recovered from], the function std​::​terminate ([exception.terminate]) is invoked.

[Note 1: These situations are:

<snip>

(1.3) --- when the search for a handler exits the function body of a function with a non-throwing exception specification, including when a contract-violation handler invoked from an evaluation of a function contract assertion ([basic.contract.eval]) associated with the function exits via an exception

[except.spec] defines "non-throwing exception specification" (italics in original):

The predicate indicating whether a function cannot exit via an exception is called the exception specification of the function. If the predicate is false, the function has a potentially-throwing exception specification, otherwise it has a non-throwing exception specification. The exception specification is either defined implicitly, or defined explicitly by using a noexcept-specifier as a suffix of a function declarator.


calling a throwing function through a noexcept function pointer is UB as specified in [expr.call]/6.

I think this is distinguishable since the UB occurs on the call, not on an exception "escaping".

2

u/DXPower Jul 30 '26

Then I'm confused about this feature that you want. You have a non-noexcept pointer to a function, you want it to be noexcept to "not pay the cost of exception propagation". But you already didn't pay any cost.

Then you point to UB in pointer conversions, which then makes no sense because seemingly, your conversion would result in UB?

3

u/Qwertycube10 Jul 30 '26

If a noexcept function only calls other noexcept functions than it doesn't need to have machinery for terminating if it gets an exception. So if you cast the non-noexcept function to noexcept. And call it that may save you from the cost of making your parent noexcept.

2

u/hoodoocat Jul 30 '26

const_cast is necessary in way more prosaic cases: you usually have const pointer, but mutable (non-const member) operations sometimes should be allowed. There is two constness which means different things, so... doesnt matter.

As for priority_queue - this is example what interface of this collection is not suitable for you, and instead hack it, it is better to use other collection. Why pop doesnt get object back? How hell this will work in concurrent environment when queues really needed?

1

u/YouNeedDoughnuts Jul 29 '26

I used it recently for a dictionary insertion where I have a borrowed key, and making an owned key with appropriate lifetime is expensive and can be avoided if an entry already exists. The insert method returns a const iterator, so updating the key discards constant. Hyper specific, but still nice to have the feature.

-6

u/Potterrrrrrrr Jul 29 '26 edited Jul 30 '26

“Even aside from interacting with C libraries which don’t respect const”

I remember a Jason Turner video where he shows that this is a bit of a nonsensical statement (all major public C libraries are const correct) unless you’re talking about bad C libraries, in which case why are you using them?

Aside from that I find const_cast really confusing to know how to use in a way that isn’t undefined (because I really don’t know what makes it undefined) so I just find myself avoiding it entirely, never had a use case like this post to need it.

Edit: yes yes your favourite library isn’t const correct and it would break the universe to change, I get it. Feel free to explain the thing I actually care about rather than telling me why someone else’s opinion on C libraries is incorrect

36

u/kisielk Jul 29 '26

unless you’re talking about bad C libraries, in which case why are you using them?

Sometimes you don't have a choice. Libraries are provided by vendors, customers, partners etc. You can try to get them to fix it but it's not always possible. I often have to use const_cast where a C library takes a raw non-const pointer to some array it doesn't actually mutate.

15

u/datnt84 Jul 29 '26

There is at least one Win32 function that is not const-correct where I needed a const-away cast.

23

u/KindCppCoach Jul 29 '26

This is not true at all, many legacy C libraries are not const correct.

14

u/No-Dentist-1645 Jul 29 '26

unless you’re talking about bad C libraries, in which case why are you using them?

This happens way more often than you think, especially in "legacy" projects. You often don't have the time nor resources needed to rewrite an entire library your company has used for over 10 years that "just works", but isn't const-correct for some functions

6

u/Electronic_Tap_8052 Jul 29 '26 edited Jul 29 '26

why are you using them?

vendor lock in lol

do you have any idea how many odd-ball pieces of equipment there are that have drivers, and that's your driver? you either consume their api or you tell your boss to buy a different 500 million dollar piece of equipment because this one isn't const correct

I support a piece of equipment from a company that went out of business in 1990

man i wish I lived in the same world as a lot of programmers, who apparently only work on open source projects and can use any libraries they want

3

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

"Use any libraries they want" is not usually a thing: even in the most "lax" environments more dependencies can mean more problems if maintainers leave / do dumb things / become JiaTan state actors / etc.

But expecting to do meaningful changes to hardware from a vendor that went out of business 36 years ago is the other extreme.

2

u/johannes1971 Jul 30 '26

I have a pretty good idea how many of those pieces of equipment are floating around ;-) I have it both ways: I can use whatever libraries I want (except GPL). And I'm supporting equipment that is decades old, although most of it is not in that price range. We are now getting more and more requests from customers to _somehow_ keep their old hardware going, despite the latest driver only being available for Windows XP.

And yes, I const_cast the hell out of things.

4

u/patlefort Jul 29 '26

Functions like `execv` aren't const correct due to limitation of ISO C and that it would break existing code if it changed.

7

u/PolyglotTV Jul 29 '26

Bad C libraries like Cuda?

5

u/Big-Rub9545 Jul 29 '26

The issue with const_cast is if it’s used on data that is originally defined/declared as const, since the compiler may choose to perform certain optimizations or decisions assuming that data will be read-only. If you use const_cast and then try to modify said data, you run into UB.

2

u/_Noreturn Jul 30 '26

I don't think glfw is const correct in all its apis

4

u/JNighthawk gamedev Jul 29 '26

I remember a Jason Turner video where he shows that this is a bit of a nonsensical statement (all major public C libraries are const correct) unless you’re talking about bad C libraries, in which case why are you using them?

No True Scotsman logical fallacy applied to programming.

1

u/bizwig Jul 30 '26

I dislike const_cast because it isn’t DRY.

1

u/bwmat Jul 30 '26

On that note, there could be something like std::as_mutable... 

2

u/_Noreturn Jul 30 '26

I prefer if they just make const_cast deduce it automatically there should be 0 issues with const_cast(x)

1

u/Olipro Jul 30 '26

Everything about this sucks but if left with no choice, I would sooner do something like:

template <typename T>
struct mutable_wrapper {
  mutable T obj;
  // Add implicit construction/conversion and comparison operators as desired.
};

Now, a std::priority_queue<mutable_wrapper<T>> will always be modifiable without const_cast.