views:

360

answers:

10

Would it make sense to have a "constify" operation in C/C++ that makes a variable const?

Here is an example where it could be useful, where obviously we don't want to declare it const yet in the first line:

std::vector<int> v;
v.push_back(5);
constify v; // now it's const

Currently, without such a possibility, you'd have to introduce another variable to get the same effect:

std::vector<int> v0;
v0.push_back(5);
const std::vector<int>& v = v0;

That's more confusing since it adds a new name into the scope and you need to make it a reference to avoid copying the whole vector (or use swap?).

+18  A: 

Frankly, I find it less confusing if a variable is either const or not, than if this can change.


To elaborate a bit on this: The reason you usually want to do this is because you cannot initialize a const variable the way you want to. std::vector is a good example of this. Well, for once, the next standard introduces a universal initialization syntax that makes this possible:

const std::vector<int> cvi = { 1, 2, 3, 4, 5, 42 }; 

However, even without C++1x' stuff at hand, and even with types that disallow this initialization syntax, you can always create a helper function to do what you want:

const std::vector<int>& cvi = create_my_vector();

or, if you want to be fancy:

const std::vector<int>& cvi = compile_time_list<1,2,3,4,5,42>::create_vector();

Note the &. There's no point in copying the result of the function call, since binding an rvalue to a const reference extends its lifetime until the end of the reference's lifetime.
Of course, recompiling with a compiler that supports C++1x' move semantics will render such optimizations pretty much needless. But binding an rvlaue to a const reference might still be faster than moving a vector and is unlikely to be slower.
With C++1x, you might also create lambda functions doing this one the fly. C++ just provides an incredibly huge arsenal of tools. IME, no matter how hard you have thought, someone else ought to come up with yet another idea to do the same thing. And often a better one than yours.


However, IME this problem usually only comes with too much code in too few functions anyway. And then it doesn't only apply to constness, but also to similar traits - like what a reference refers to.
A classic is the use-one-of-several-possible-streams. Instead of this

int main(int argc, char* argv[])
{
  std::istream* istrm = NULL;
  std::ifstream ifs;
  if( argc > 1 )
  {
    ifs.open( argv[1] );
    if( ifs.good() ) 
      istrm = &ifs;
  }
  if( !istrm ) 
    istrm = &std::cin;

  while( istrm->good() )
  {
     // reading from *istrm implemented here
  }
  return 0;
}

just split the concerns into 1) figuring out where to read from and 2) the actual reading:

int read(std::istream& is)
{
  while( is.good() )
  {
     // reading from is implemented here
  }
  return 0;
}

int main(int argc, char* argv[])
{
  if( argc > 1 )
  {
    std::ifstream ifs( argv[1] );
    if( ifs.good() ) 
      return read(ifs);
  }
  return read(std::cin);
}

I have yet to see a real-world example of a variable that wasn't as constant as it could have been which couldn't be fixed by separating of concerns.

sbi
+1: I have to agree -- the fact that `const` doesn't apply during execution of the ctor or dtor already causes enough confusion.
Jerry Coffin
I think you mean C++0x, not C++1x. http://www2.research.att.com/~bs/C++0xFAQ.html
Ben
@Ben: The time frame for C++0x ends this year and there won't be a new standard this year. It's likely to come out next year, which would make it C++11. If there's further delays, it will be C++12. Hence the C++1x.
sbi
Did you read my link? "The name 'C++0x' is a relict of the days where I and others, hoped for a C++08 or C++09. However, to minimize confusion, I'll keep referring to the upcoming C++ standard with the feature set defined here as C++0x. Think of 'x' as hexadecimal (most likely 'B', i.e. C++11)." - Bjarne Stroustrup
Ben
@Ben: Yes, I did, long ago. However, there I disagree with him. I find it _more_ confusing when "C++0x" refers to "C++11".
sbi
+5  A: 

This is a great time to use a function

#include <vector>

std::vector<int> makeVector()
{
  std::vector<int> returnValue;
  returnValue.push_back(5);
  return returnValue;
}

int main()
{
  const std::vector<int> myVector = makeVector();
}
Bill
Your solution has different semantics. The OP wants to create 1 `vector<T>` and change the const non-const of the identifier. Your solution creates 2 copies of the `vector<T>` with 2 different levers of access
JaredPar
@Jared: I read it as: The OP wants to essentially create a constant vector, but can't due to lack of C++0x's initializer lists, or not having all of the data at once.
Bill
@Jared: This version will likely create just one vector (due to named return value optimization), which you can modify in one scope and then it becomes constant in another, just as OP desired.
UncleBens
+7  A: 

You're basically trying to reproduce the effect of a constructor -- i.e., const applies only after the constructor completes (and only until the dtor is invoked). As such, what you need is another class that wraps your vector and initializes it in the ctor. Once the ctor completes and returns, the instance becomes const (assuming, of course, that it was defined to be const).

C++0x will ameliorate the requirement for wrapping like this considerably. You'll be able to use brace initializers for vectors to create/initialize the vector in one operation. Other types will (at least potentially) support user-defined initializers to accomplish roughly the same thing.

Jerry Coffin
+6  A: 

C++ is statically typed. To me, introducing such an operation would be a violation of this paradigma and would cause much confusion.

Alexander Gessler
This is still static typing--compiler can infer when variable is const or not at compile time.
liori
@liori: Maybe. If the type of a variable can change during execution, this means we need to do execution-path analysis to do static typing. That won't work in all cases, and will cause surprising results in others.
David Thornley
Ah, yes, forgot of that. +1.
liori
+3  A: 

I have thought about this too. But, IMHO, it will create a lot of confusion, which will outweigh its benefits. Come to think of it, the whole notion of constness in C++ is already confusing enough.

Your idea boils down to "How can I make a variable read-only after it has been initialized?". You can get the same effect by making your variable a private member of a class, which is initialized in the constructor, and for which you provide a getter but no setter.

Dima
+3  A: 
Jon Purdy
Using `const_cast` this way results in undefined behavior.
Dennis Zickefoose
This is not safe, as whenever you declare a variable as `const`, the compiler may choose to put all or part of it in read-only memory, leading to faults if you cast away const and try to modify the object. This is unlikely for a complex class like `std::vector` but is still a concern.
Chris Dodd
Oh, well. I did say it wasn't safe.
Jon Purdy
I removed the incorrect section and further clarified the explanation of the `constify` implementation.
Jon Purdy
+2  A: 

I assume you're talking about something more generic than only initializing vectors (which is solved in C++0x), and use vectors only as an example.

I'd rather see it done through some kind of local functions:

const vector<int> values = []{
    vector<int> v;
    copy(some_other_data.begin(), some_other_data.end(), v);
    sort(v);
    return v;
}();

(I could mess anonymous function syntax of C++0x). This I can read quite naturally as: "prepare a const vector according to routine described here". Only amount of parentheses bother me slightly.

I can see how this code might become a C++ idiom after C++0x becomes more natural to programmers.

(edited after dehmann's suggestion)

liori
Nice answer. And yes, it seems you don't need all the parentheses in `[](){...}`. The round parentheses are optional, so it becomes `[]{...}`
A: 

You could wrap the vector in a class , declare the wrapped vector mutable, and then make a const instance of the wrapper. The wrapping class can change the vector but external callers see a const object

pm100
+1  A: 

Currently, const or not is something that the compiler knows, so the compiler won't accept a program that tries to change a const variable.

If you wanted to make a constify operator, you would have to make this a property of the variable (without further keywords, of every variable) so it can change at runtime. And of course you would have to throw an exception whenever a program tries to change a (currently) const variable, which effectively means that every write access to every variable must check the const property first.

All this goes against the philosophy of C++ and every other statically typed language. And it breaks binary compatibility with existing libs, too.

ammoQ
+1  A: 

Consider the following bit:

void foo(std::vector<int> & v) 
{
  v.push_back(1);
  constify v;
}
void bar() {
  std::vector<int> test(7);
  foo(test);
  test.clear();
}

Is the variable v in foo constified? It is the same variable as test in bar. Thus, the test.clear() call should be invalid. I think that what you really meant is that the name is "constified", not the variable.

It would be actually trivial to specify and implement: constify x; is a declaration of a const reference named x, which has the same base type as the variable x it hides. It follows the usual scope rules, except that it may be defined in the same scope as the previous x declaration.

MSalters