10

AFAIK removing constness from const variables is undefined behavior:

const int i = 13;
const_cast<int&>(i) = 42;      //UB
std::cout << i << std::endl;   //out: 13

But are const function arguments "real" constants? Let's consider following example:

void foo(const int k){
    const_cast<int&>(k) = 42;    //UB?
    std::cout << k << std::endl;
}

int main(){
    foo(13); //out: 42
}

Seems like compiler doesn't apply the same optimizations to const int k as to const int i.

Is there UB in the second example? Does const int k help compiler to optimize code or compiler just checks const correctness and nothing more?

Example

yrHeTateJlb
  • 427
  • 2
  • 20
  • Optimizations have nothing to do with something being UB or not. Further, `const` does not help with optimizations. – Acorn Oct 11 '18 at 07:26
  • 4
    @Acorn, No, UB allows compiler to not care about some border cases and generate more effective code. And, no, `const` [helps with optimizations](https://stackoverflow.com/questions/27466642/what-kind-of-optimization-does-const-offer-in-c-c-if-any) a lot – yrHeTateJlb Oct 11 '18 at 07:32
  • 3
    @Acorn optimizations have a LOT to do with UB. That's a big part of what allows them to make assumptions about the code, like leveraging strict aliasing to avoid load-hit-stores. –  Oct 11 '18 at 07:32
  • 1
    @Acorn I think `const` helps with optimizations to a certain degree, say, a lot. https://youtu.be/zBkNBP00wJE?t=26m55s –  Oct 11 '18 at 07:34
  • For the most part `const` is a rule you impose to yourself, to throw a compile error (warning?) if you try to overwrite data you're not supposed to. It really helps in big projects. For example, if you have a `char*` to a string literal, it makes sense to make it `const` so you don't get run time errors if you do something like pass it to `strtok()`. I'm fairly certain the optimizer can use that to make some decisions too. – Havenard Oct 11 '18 at 07:34
  • 2
    @yrHeTaTeJlb: Sorry, but you haven't understood. UB provides opportunities for optimization, but optimizations do not have anything to do with something being UB or not. – Acorn Oct 11 '18 at 07:35
  • Also if you want to pass an object by reference to a function (for performance reasons) but the function is not supposed to modify it, you can make the parameter `const`. If you inadvertently write something in the function that modifies the object it will throw a compile error. – Havenard Oct 11 '18 at 07:41
  • 3
    You know how we tell people that appearing to "work" is the worst manifestation of UB? That. – StoryTeller - Unslander Monica Oct 11 '18 at 07:43
  • 1
    @NickyC: That is an extreme example where `const` is applied to an object with static storage duration. Adding `const` to a local integer variable like here is not going to do anything, because the compiler already knows if something is a constant or not. I am not saying do not use `const`, I am saying it is not a magical way of enabling optimizations as people usually think. – Acorn Oct 11 '18 at 07:44
  • "*But are const function arguments "real" constants?*" - Irrelevant. "*Is there UB in the second example?*" - Yes, because "*removing constness from const variables is undefined behavior*". "*Does const int k help compiler to optimize code*" - Which compiler? Also, this has nothing to do with whether your code has UB or not. – melpomene Oct 11 '18 at 07:47
  • @Acorn Maybe your definition of "help" looks a little bit too narrow, and your definition of "extreme" is a little bit wide. I am saying I feel your first comment is a little bit misleading by talking about a particular case in a tone that sounds a little bit too general. –  Oct 11 '18 at 07:57
  • @NickyC: Sorry, no. If you are adding `const` to types simply because you think it will help with performance, you are doing it wrong. Rather, if you care about compile-time constants and precomputing values, you should be using the proper tools for that (e.g. `constexpr`, TMP, code generation...). Further, if you *really* cared about performance, adding `const` is simply the last of the last of the last of things you should be thinking about (and that is assuming there weren't better methods, which there are). – Acorn Oct 11 '18 at 19:28
  • @Acorn Did I say anything sounded like mistaking performance as the primary purpose of const-qualifier? It didn't seem so. At least not to me. And I think my last comment has highlighted my actual opinion. I don't think there is anything else to clarify. –  Oct 12 '18 at 01:53

3 Answers3

5

The i in const int i = 13; can be used as constant expression (as template argument or case label) and attempts to modify it are undefined behavior. It is so for backwards compatibility with pre-C++11 code that did not have constexpr.

The declarations void foo(const int k); and void foo(int k); are declaring same function; the top level const of parameters does not participate in function's signature. Parameter k must be passed by value and so can't be "real" constant. Casting its constness away is not undefined behavior. Edit: But any attempt to modify it is still undefined because it is const object [basic.type.qualifier] (1.1):

A const object is an object of type const T or a non-mutable subobject of such an object.

By [dcl.type.cv] 4 const object can't be modified:

Except that any class member declared mutable (10.1.1) can be modified, any attempt to modify a const object during its lifetime (6.8) results in undefined behavior.

And since function parameters are with automatic storage duration a new object within its storage can't be also created by [basic.life] 10:

Creating a new object within the storage that a const complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior.

It is unclear why k was declared const at the first place if there is plan to cast its constness away? The only purpose of it feels to be to confuse and to obfuscate.

Generally we should favor immutability everywhere since it helps people to reason. Also it may help compilers to optimize. However where we only declare immutability, but do not honor it, there it works opposite and confuses.

Other thing that we should favor are pure functions. These do not depend on or modify any external state and have no side-effects. These also are easier to reason about and to optimize both for people and for compilers. Currently such functions can be declared constexpr. However declaring the by-value parameters as const does not help any optimizations to my knowledge, even in context of constexpr functions.

Öö Tiib
  • 10,809
  • 25
  • 44
  • Actually it may help more than confuse. McConnell in his Code Complete says that function arguments should be immutable, it makes code more clear. But I asked this question because my colleague thinks that `void foo(const int k)` helps compiler to optimize code. For me his statement is dubious – yrHeTateJlb Oct 11 '18 at 08:21
  • @yrHeTaTeJlb I disagree with McConnell: why does it matter if arguments are immutable or not if they are *copies*? Especially if the remedy is to copy them again and mutate that copy. – Caleth Oct 11 '18 at 08:26
  • @Caleth, the point is that arg `const int size` means _size of smth._ throughout the function body. And you don't need to doubt has been some arg changed or not. – yrHeTateJlb Oct 11 '18 at 09:02
  • @yrHeTaTeJlb yes, but what about `Iter foo(Iter first, Iter last) { for (; first != last; ++first) {/* stuff */} return first; }`? – Caleth Oct 11 '18 at 09:05
  • @yrHeTaTeJlb I meant with "to confuse and to obfuscate" that when you declare const but then cast it away then that can be major source of confusion. It is **far** worse than not to have const at first place. I updated the answer. – Öö Tiib Oct 11 '18 at 10:05
  • To be clear: casting away constness is _not_ undefined behaviour. It's the subsequent assignment to it that invokes undefined behaviour. – Andre Kostur Oct 11 '18 at 13:48
  • @AndreKostur yes, I add some quotes of standard. – Öö Tiib Oct 11 '18 at 13:57
4

But are const function arguments "real" constants?

I think the answer is yes.

They won't be stored in ROM (for example), so casting away const will at least appear to work OK. But my reading of the standard is that the the parameter's type is const int and therefore it is a const object ([basic.type.qualifier]), and so modifying it is undefined ([dcl.type.cv]).

You can confirm the parameter's type fairly easily:

static_assert( std::is_same_v<const int, decltype(k)> );

Is there UB in the second example?

Yes, I think there is.

Does const int k help compiler to optimize code or compiler just checks const correctness and nothing more?

In theory the compiler could assume that in the following example k is not changed by the call to g(const int&), because modifying k would be UB:

extern void g(const int&);

void f(const int k)
{
  g(k);
  return k;
}

But in practice I don't think compilers take advantage of that, instead they assume that k might be modified (compiler explorer demo).

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
0

I'm not sure what you mean by a "real" const, but here goes.

Your const int i is a variable declaration outside of any function. Since modifying that variable would cause Undefined Behaviour, the compiler gets to assume that its value will never change. One easy optimization would be that anywhere you read from i, the compiler doesn't have to go and read the value from main memory, it can emit the assembly to use the value directly.

Your const int k (or the more interesting const int & k) is a function parameter. All it promises is that this function won't be changing the value of k. That doesn't mean that it cannot be changed somewhere else. Each invocation of the function could have a different value for k.

Andre Kostur
  • 770
  • 1
  • 6
  • 15