Is there some ninja trick to make a variable constant after its declaration?

32,183

Solution 1

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;
  }();
  ...
}

Solution 2

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.

Solution 3

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);
    ....

}

Solution 4

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&

Solution 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.
    }
}
Share:
32,183
Andreas Bonini
Author by

Andreas Bonini

Updated on July 11, 2022

Comments

  • Andreas Bonini
    Andreas Bonini almost 2 years

    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.

  • Andreas Bonini
    Andreas Bonini over 13 years
    That's a really interesting idea. I already started using lambda functions, but I never thought of using them this way. =)
  • Steve Jessop
    Steve Jessop over 13 years
    Can you define and call a lambda in place? const int a = [&]() -> int { ... }();? I don't have a compiler to hand that supports them.
  • Mark Ransom
    Mark Ransom over 13 years
    You could do the same thing with references too.
  • Dennis Zickefoose
    Dennis Zickefoose over 13 years
    @Steve: gcc45 doesn't seem to object to that notation, although I'm not prepared to back that up with any actual proof.
  • JaredPar
    JaredPar over 13 years
    @Steve, I do not know if C++ supports inline lambdas but it would definitely be nice for the exact reason you displayed.
  • mip
    mip over 13 years
    This silently smuggles the fact that a is actually a different object.
  • josesuero
    josesuero over 13 years
    could use a const reference instead, in the cases where it matters
  • vhallac
    vhallac over 13 years
    @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. :)
  • GManNickG
    GManNickG over 13 years
    @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.)
  • JaredPar
    JaredPar over 13 years
    @GMan, what is the type of a lambda expression in C++0x? Is it a binding of a template like C#'s Func<T> or something else entirely.
  • mip
    mip over 13 years
    @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.
  • GManNickG
    GManNickG over 13 years
    @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.
  • JaredPar
    JaredPar over 13 years
    @GMan, interesting. Sounds like it's remarkably similar to VB.Net's anonymous delegates.
  • AshleysBrain
    AshleysBrain over 13 years
    This is my favourite solution - don't forget to use a const reference for larger objects though.
  • mb14
    mb14 over 13 years
    which one, the first one or the second one .
  • Ates Goral
    Ates Goral over 13 years
    @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
    Ates Goral over 13 years
    +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.
  • Andreas Magnusson
    Andreas Magnusson over 13 years
    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
    Andreas Magnusson over 13 years
    Wow, very, very confusing. Consider working on a couple of hundred kloc project with code like this. Nightmare. KISS.
  • mb14
    mb14 over 13 years
    @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 ;-).
  • Andreas Magnusson
    Andreas Magnusson over 13 years
    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.
  • Ruslan
    Ruslan over 6 years
    You forgot the T const& v_; inside Constify.
  • Ayxan Haqverdili
    Ayxan Haqverdili over 4 years
    @doc you could make it a const reference as in const int& a = ComputeA(a_in);, then it would be the same object.
  • mip
    mip about 4 years
    @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.