Would it make sense to have a 'constify' operation in C++?

女生的网名这么多〃 提交于 2019-12-05 06:31:05

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.

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.

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

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

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.

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)

It's already been mentioned that C++0x solves this somewhat with brace-initialisers:

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

Though this only allows for initialisation, and does not allow, for instance, invoking non-const member functions after the constructor has run. It is possible to define a macro constify as follows:

#define constify(type, id) \
for (type const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

Which can be used like so:

std::vector<int> v;

// v is non-const here.

constify (std::vector<int>, v) {

    // v is const here.

}

This works by setting up a for loop that executes the following statement or block only once, with the constified variable local to the loop body. Note the declaration of helper variable i_const before the local i: the statement int const& i(i) initialises i to itself—that is, to an uninitialised value—and we want the (i) to refer instead to the previously declared i, so an extra level is needed.

If you can make use of C++0x features, the decltype keyword comes in handy, allowing you to omit the type from invocations of constify:

#define constify(id) \
for (decltype(id) const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

Which lets you write, simply:

constify (v) {
    // ...
}

Both versions work whether the variable is initially declared const or not. So, yes, something very like what you were looking for is indeed possible, but probably altogether not worth it.

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.

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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!