75

I know the answer is 99.99% no, but I figured it was worth a try, you never know.

void SomeFunction(int a)
{
    // Here some processing happens on a, for example:
    a *= 50;
    a %= 10;
    if(example())
       a = 0;
    // From this point on I want to make "a" const; I don't want to allow
    // any code past this comment to modify it in any way.
}

I can do something somewhat similar with const int b = a;, but it's not really the same and it creates a lot of confusion. A C++0x-only solution is acceptable.

EDIT: another less abstracted example, the one that made me ask this question:

void OpenFile(string path)
{
    boost::to_lower(path);
    // I want path to be constant now
    ifstream ...
}

EDIT: another concrete example: Recapture const-ness on variables in a parallel section.

Community
  • 1
  • 1
Andreas Bonini
  • 44,018
  • 30
  • 122
  • 156
  • 2
    Are you looking for a compile-time or run-time protection? – Lior Kogan Sep 08 '10 at 15:28
  • 3
    @Lior: a compile-time protection, of course. C++ doesn't protect anything at all at run-time, and while in theory somewhat possible it's really unfeasible and useless =) – Andreas Bonini Sep 08 '10 at 15:37
  • 4
    Your second example is terrible. There is absolute no reason, other than intentionally breaking portability, to convert a file path to lowercase. – Juliano Sep 08 '10 at 15:55
  • 4
    @Juliano: Suppose that all your actual file names are lower case (because you created them), but you want references to them to be case-insensitive. – Steve Jessop Sep 08 '10 at 16:22
  • 1
    After few lines you might have change your mind and say - I want `a` to be non-const. And then again makei it const, and so on. How the code would look like if every programmer had thought in this way? Constant should mean - **always constant**. Another solution (to posted below) might be declaring `a` as `const int a` and use `const_cast` every time you modify it. But it's an abuse!!! if you just don't want to modify `a` just don't modify it, there's no need to additionaly mark it as `const`, which BTW is a compile-time qualifier. – mip Sep 08 '10 at 16:39
  • @Doc: This is essentially what the compiler already does whenever you define a const variable. How do you suppose constructors work on const objects? This is mearly an extension of that... set up your data, and then let the compiler catch any accidental attempts to further modify it. – Dennis Zickefoose Sep 08 '10 at 16:49
  • @Dennis Zickefoose: then set it up in the constructor! You are going to violate Occam's razor, not mentioning side-effects, this "extension" would bring. – mip Sep 08 '10 at 17:01
  • @Juliano: Your way requires enumerating the filesystem in the program. You said "there is no reason to convert to lowercase", not "it's possible to avoid lowercase, perhaps by writing extra code and sacrificing performance". As for moving the files: for that matter, somebody might delete the files, or fill them with random bytes. If someone changes the filenames, then the program can't read the files - what's the problem? Any program can take a certain amount of abuse before failing, that's not a bug as long as the restrictions are reasonable. – Steve Jessop Sep 08 '10 at 17:13
  • @doc: What constructor? The object is already constructed before you get access to it. I use such a construct fairly regularly, but with references and a second name. Its not a particularly useful feature, but its hardly a crime against clean code either. – Dennis Zickefoose Sep 08 '10 at 17:33
  • @Steve: Not really. Just work with the filename as it is, without lowercasing anything, and let the operating system find the file for you when you ask it. Performance is only a problem after you profiled your application and verified that that code is a bottleneck. Otherwise, it is really unnecessary and wrong. – Juliano Sep 08 '10 at 17:36
  • @Dennis Zickefoose: just read again my first comment. If object is supposed to be constant and you need to set it up, use a constructor. If object needs "second" setup then it's not a const object. If so, why not third, fourth, fifts setup? It's pointless. – mip Sep 08 '10 at 17:46
  • @Juliano: OK, I said, "suppose you want references to [your files] to be case-insensitive", and you say, "no, I refuse to do that. I want filename references to be case-sensitive in my program". The computer is hardly going to "find the file for me" if I *don't* lowercase it, given my functional requirements. So, you provide a means of making that spec change, guaranteed to work in all organisations ever, and to be easier than calling `boost::to_lower`, and I'll admit you were right to say there's never any reason to lowercase a filename ;-) – Steve Jessop Sep 08 '10 at 17:48
  • @Doc: The "setup" in this case doesn't refer to constructing the object, but to setting up the function. Why create a second extraneous copy, when you could simply modify the first? The rest of your argument is just that you're afraid of slopes, so it really doesn't matter. – Dennis Zickefoose Sep 08 '10 at 17:58
  • @Dennis Zickefoose: Previously you have said that it's an extension of constructing object. So does it refer to constructing or not? "Why create a second extraneous copy, when you could simply modify the first" - You can modify an object after construction, but then it's not const! – mip Sep 08 '10 at 18:11
  • A little background since you seem so interested in the second example: I can't go into much detail of what I'm doing but I work at wowhead.com, where we take data from World of Warcraft (a game) and display it on our website. The data in the game is stored in a proprietary archive format called MPQ; this archive is case insensitive. Rather than working with the archive we extract the files. Those files then reference other files in the archive, such as "/Interface.txt", but since we use a case sensitive file system we have to lower case everything since sometimes you may find "/INTERFACE.TXT" – Andreas Bonini Sep 08 '10 at 18:13
  • @Steve: You are quite sad, really. I never said that references have to be case-sensitive; you are jamming your words in my mouth to make me look wrong. Sad. The operating system *will* find the file for you given that you provide it the correct name. If the filesystem is case-insensitive, great. The lowercasing is unnecessary. If the filesystem is case-sensitive, fine, the original reference must already be compatible with the correct casing of the filename, and there is no need for lowercasing. If you can't understand that, I'm out of this discussion. – Juliano Sep 08 '10 at 18:15
  • @Juliano: normally yes, but mine is a special case. We have no control over the MPQ format or the files therein contained. – Andreas Bonini Sep 08 '10 at 18:17
  • @Andreas: Now it seems more clear. WoW is a Windows game, it is expected that filenames be case-insensitive in Windows. Looks like you are processing these files in a non-Windows system. This is really an exceptional case. You may expect this sort of incompatibilities to happen. Though if the game creates a /Interface.txt file and references it as /INTERFACE.TXT elsewhere, it is still at least bad coding style (in WoW). – Juliano Sep 08 '10 at 18:25
  • @Doc: Its an extension of the idea of a constructor. First, you setup your object, then you mark it const. This is the exact sequence that the compiler utilizes to allow constructors to work properly. Extend that to a function parameter... the constructor is already called before you see it, so that is out of the question. You only need to change it once, and you want the compiler to stop you from changing it a second time. You have to jump through hoops to get that behavior, but it can simplify code and make it more safe since the compiler is doing your checks for you. – Dennis Zickefoose Sep 08 '10 at 18:31
  • @Dennis Zickefoose: some time ago I realized that there should be const constructors in C++. Probably then you would understand me. IMO `const` is generally overused, misused and flaw in C++. So that's my opinion. Now, from your "extension" point of view, constructors are not needed any more, since you can make all fields public and setup your object by a function. Are you sure this is the way to go? Of course, you can write helper function for a clarity, that will compute and return some value. But don't tell me it's equivalent of constructor or making an object const. – mip Sep 08 '10 at 20:11
  • @Juliano: I'm willing to be out too. I think the hypothetical case I outlined, is pretty much exactly Andreas' actual situation. He's found a reason to lowercase a filename other than "intentionally breaking portability" - a fairly obvious reason IMO, but maybe that's because I'm such a "sad" computer nerd ;-p Or because I've played this game before of compatibility layers between POSIX-like and Windows-like filesystems. – Steve Jessop Sep 08 '10 at 23:52
  • @Juliano: Use case for converting a path to lowercase: You're not using the path to actually access a file but you want to extract "meta" information from the path. This could be as simple as checking the file extension to determine the file type etc. – Ates Goral Sep 09 '10 at 04:53
  • 1
    possible duplicate of [Would it make sense to have a 'constify' operation in C++?](http://stackoverflow.com/questions/3568166/would-it-make-sense-to-have-a-constify-operation-in-c) – MSalters Sep 09 '10 at 09:24

8 Answers8

51

One solution would be to factor all of the mutation code into a lambda expression. Do all of the mutation in the lambda expression and assign the result out to a const int in the method scope. For example

void SomeFunction(const int p1) { 
  auto calcA = [&]() {
    int a = p1;
    a *= 50;
    a %= 10;
    if(example())
       a = 0;
    ..
    return a;
  };
  const int a = calcA();
  ...
}

or even

void SomeFunction(const int p1) { 
  const int a = [&]() {
    int a = p1;
    a *= 50;
    a %= 10;
    if(example())
       a = 0;
    ..
    return a;
  }();
  ...
}
Johan
  • 74,508
  • 24
  • 191
  • 319
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • That's a really interesting idea. I already started using lambda functions, but I never thought of using them this way. =) – Andreas Bonini Sep 08 '10 at 15:33
  • 5
    Can you define and call a lambda in place? `const int a = [&]() -> int { ... }();`? I don't have a compiler to hand that supports them. – Steve Jessop Sep 08 '10 at 16:32
  • 2
    @Steve: gcc45 doesn't seem to object to that notation, although I'm not prepared to back that up with any actual proof. – Dennis Zickefoose Sep 08 '10 at 16:54
  • 1
    @Steve, I do not know if C++ supports inline lambdas but it would definitely be nice for the exact reason you displayed. – JaredPar Sep 08 '10 at 16:59
  • 1
    @Steve @Dennis @Jared: That should be legal. A lambda expression is a primary expression that when evaluated results in a temporary closure object. (And then you just call it.) – GManNickG Sep 08 '10 at 19:55
  • @GMan, what is the type of a lambda expression in C++0x? Is it a binding of a template like C#'s `Func` or something else entirely. – JaredPar Sep 08 '10 at 20:16
  • @Jared: The type is a so-called closure type and is a unique, unnamed, non-union, non-aggregate class type. This type has to follow certain properties (obviously), but is otherwise unspecified. – GManNickG Sep 08 '10 at 22:48
  • @GMan, interesting. Sounds like it's remarkably similar to VB.Net's anonymous delegates. – JaredPar Sep 08 '10 at 22:51
48

You could move the code to generate a into another function:

int ComputeA(int a) {
  a *= 50;
  a %= 10;
  if (example())
    a = 0;
  return a;
}

void SomeFunction(const int a_in) {
  const int a = ComputeA(a_in);
  // ....
}

Otherwise, there's no nice way to do this at compile time.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • 7
    This silently smuggles the fact that `a` is actually a different object. – mip Sep 08 '10 at 18:04
  • 4
    could use a const reference instead, in the cases where it matters – jalf Sep 08 '10 at 19:01
  • 1
    @doc In my opinion an `a` you can change and an `a` that you cannot change are already different objects. Might as well reflect that in code. :) – vhallac Sep 08 '10 at 19:30
  • @jalf: const reference to what? When `a_in` is `const int` you can not pass it by non-const reference. When it's not, you don't gain anything. – mip Sep 08 '10 at 20:23
  • @Dysaster: But `const` is merely a contextual, compile-time contract. The same object can have two facets (one const, one non-const) in two different contexts. – Ates Goral Sep 09 '10 at 05:00
  • @doc you could make it a const reference as in `const int& a = ComputeA(a_in);`, then it would be the same object. – Aykhan Hagverdili Jan 20 '20 at 21:04
  • @Ayxan no, cause `a_in` is passed by value, which means that `ComputeA` already operates on a copy and not the same object as `a_in`. – mip Mar 27 '20 at 11:12
12

A pattern I used to use is to "hide" the argument with an _, so the code becomes

void SomeFunction(int _a)
{
    // Here some processing happens on a, for example:
    _a *= 50;
    _a %= 10;
    if(example())
       _a = 0;

    const int a = _a;
    // From this point on I want to make "a" const; I don't want to allow
    // any code past this comment to modify it in any way.
}

You could also use only const variables and make a function to compute the new value of a, if necessary. I tend more en more to not "reuse" variables en make as much as possible my variables immutable : if you change the value of something , then give it a new name.

void SomeFunction(const int _a)
{
    const int a = preprocess(_a);
    ....

}
mb14
  • 22,276
  • 7
  • 60
  • 102
  • This is my favourite solution - don't forget to use a const reference for larger objects though. – AshleysBrain Sep 08 '10 at 22:54
  • which one, the first one or the second one . – mb14 Sep 08 '10 at 23:04
  • 1
    Now you have increased the complexity of the function by introducing a second variable that means the same thing as another? Very confusing for someone new to your code. I would strongly recommend one of the more popular solutions that moves the initialization code to another function (or lambda). – Andreas Magnusson Sep 10 '10 at 14:52
  • 2
    @Andreas: introducing a new function is also introducing complexity and using a lambda seems overhelming. Notice that *every* solution involving a function introduces as well a second variable. My solution can be seen as lightweighted lambda version. However, personally I would probably use a function too or not put myself in that kind of situation. But the question is ask ninja trick , so i gave a ninja trick ;-). – mb14 Sep 10 '10 at 15:04
  • 1
    Even if the question was asking for some sort of ninja trick, the idea is to increase readability. A const local variable simplifies the code since the reader can rest assured that its value won't change later in the function. Your solution is a ninja stab in the back since that assumption no longer holds (especially if you follow the "tip" from AshleysBrain). The complexity is not just in the number of variables, it's also in the clarity of what they represent. By increasing the number of variables with no clear meaning, you impose an extra mental burden on the reader. – Andreas Magnusson Sep 13 '10 at 07:29
11

Why not refactor your code in to two separate functions. One that returns a modified a and another that works on this value (without ever changing it).

You could possibly wrap your object too around a holder class object and work with this holder.

template <class T>
struct Constify {
    Constify(T val) : v_( val ) {}
    const T& get() const  { return v_; }
};

void SomeFuncion() {
    Constify ci( Compute() ); // Compute returns `a`
    // process with ci
}

Your example has an easy fix: Refactoring.

// expect a lowercase path or use a case insensitive comparator for basic_string
void OpenFile(string const& path)  
{        
    // I want path to be constant now
    ifstream ...
}

OpenFile( boost::to_lower(path) ); // temporaries can bind to const&
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • 2
    +1 The fact that the function has two separate contexts with different const-ness requirements is a clear sign that the function needs to be split into two functions. – Ates Goral Sep 09 '10 at 05:04
  • You forgot the `T const& v_;` inside `Constify`. – Ruslan Nov 24 '17 at 12:13
6

this might be one way to do it, if you are just trying to avoid another name. i suggest you think twice before using this.

int func ()
{
    int a;
    a %= 10;

const int const_a = a;
#define a const_a

    a = 10;  // this will cause an error, as needed.
#undef a
}
alvin
  • 1,176
  • 8
  • 15
5

I don't actually suggest doing this, but you could use creative variable shadowing to simulate something like what you want:

void SomeFunction(int a)
{
    // Here some processing happens on a, for example:
    a *= 50;
    a %= 10;
    if(example())
       a = 0;
    {
        const int b = a;
        const int a = b;  // New a, shadows the outside one.
        // Do whatever you want inside these nested braces, "a" is now const.
    }
}
Mark B
  • 95,107
  • 10
  • 109
  • 188
0

Answers were pretty solid, but honestly I can't really think of a GOOD situation to use this in. However in the event you want to Pre-Calculate a constant which is basically what you are doing you have a few main ways You can do this.

First we can do the following. So the compiler will simply set CompileA# for us in this case it's 50, 100, and 150.

const int CompileA1 = EarlyCalc(1);
const int CompileA2 = EarlyCalc(2);
const int CompileA3 = EarlyCalc(3);

int EarlyCalc(int a)
{
    a *= 50;
    return a;
}

Now anything beyond that there's so many ways you can handle this. I liked the suggestion as someone else had mentioned of doing.

void SomeFunc(int a)
{
    const int A = EarlyCalc(a);
    //We Can't edit A.
}

But another way could be...

SomeFunc(EarlcCalc(a));

void SomeFunc(const int A)
{
    //We can't edit A.
}

Or even..

SomeFunction(int a)
{
    a *= 50;
    ActualFunction(a);
}

void ActualFunction(const int A)
{
    //We can't edit A.
}
Jeremy Trifilo
  • 456
  • 6
  • 11
-1

Sure, there is no way to do it using the same variable name in C++.

Oleg
  • 232
  • 1
  • 9