views:

209

answers:

7

EDIT: this question could probably use a more apropos title. Feel free to suggest one in the comments.

In using C++ with a large class set I once came upon a situation where const became a hassle, not because of its functionality, but because it's got a very simplistic definition. Its applicability to an integer or string is obvious, but for more complicated classes there are often multiple properties that could be modified independently of one another. I imagine many people forced to learn what the mutable keyword does might have had similar frustrations.

The most apparent example to me would be a matrix class, representing a 3D transform. A matrix will represent both a translation and a rotation each of which can be changed without modifying the other. Imagine the following class and functions with the hypothetical addition of 'multi-property const'.

class Matrix {
     void translate(const Vector & translation) const("rotation");
     void rotate(const Quaternion & rotation) const("translation");
}

public void spin180(const("translation") & Matrix matrix);
public void moveToOrigin(const("rotation") & Matrix matrix);

Or imagine predefined const keywords like "_comparable" which allow you to define functions that modify the object at will as long as you promise not to change anything that would affect the sort order of the object, easing the use of objects in sorted containers.

What would be some of the pros and cons of this kind of functionality? Can you imagine a practical use for it in your code? Is there a good approach to achieving this kind of functionality with the current const keyword functionality?

Bear in mind

  • I know such a language feature could easily be abused. The same can be said of many C++ language features
  • Like const I would expect this to be a strictly compile-time bit of functionality.
  • If you already think const is the stupidest thing since sliced mud, I'll take it as read that you feel the same way about this. No need to post, thanks.

EDIT: In response to SBK's comment about member markup, I would suggest that you don't have any. For classes / members marked const, it works exactly as it always has. For anything marked const("foo") it treats all the members as mutable unless otherwise marked, leaving it up to the class author to ensure that his functions work as advertised. Besides, in a matrix represented as a 2D array internally, you can't mark the individual fields as const or non-const for translation or rotation because all the degrees of freedom are inside a single variable declaration.

+1  A: 

Such high level concepts are useful for a programmer.

If I wanted to make const-ness fine-grained, I'd do it structurally:

struct C { int x; int y; };

C const<x> *c;
C const<x,y> *d;
C const& e;
C &f;

c=&e; // fail, c->y is mutable via c
d=&e;
c=&f;
d=c;

If you allow me to express a preference for a scope that maximally const methods are preferred (the normal overloading would prefer the non-const method if my ref/pointer is non-const), then the compiler or a standalone static analysis could deduce the sets of must-be-const members for me.

Of course, this is all moot unless you plan on implementing a preprocessor that takes the nice high-level finely grained const C++ and translates it into casting-away-const C++. We don't even have C++0x yet.

wrang-wrang
+5  A: 

In cases where you have groups of members that are either const together or mutable together, wouldn't it make as much sense to formalize that by putting them in their own class together? That can be done today without changing the language.

T.E.D.
Part of why I chose the example of a matrix is specifically because that can't be done. You can't push the translation and rotation functionality of a matrix into two separate classes. Similarly, in the sort example, you can't necessarily push the comparability attributes out of a class.
Jherico
You know, in C++ there still is that interesting little tidbit called a Function which allows you to pull the method out of the class. ;)
Billy ONeal
I'm not sure how that would apply to the example above. Can you give a more precise answer?
Jherico
I looked at a Matrix class and it appeared that translate affects the 4th row in a 4x4 array and rotate affects the first three (sorry, don't know the math). Wouldn't it be possible to separate it into one 3x4 and one 1x4 array (or an accessor could return references into the single array member)? If so you could declare a free translate function: void translate(translate_part and call it with something like: translate(my_matrix.get_translate_part(), my_vector); - Now translate can't touch the rest of Matrix, if you feel this kind of fine-grainedness is a great benefit.
UncleBens
The point is to add functionality to the language, not redesign this one example, particularly not in a way that might make the implementation of every other function a living hell.
Jherico
+6  A: 

Scott Meyers was working on a system of expanding the language with arbitary constraints (using templates).

So you could say a function/method was Verified,ThreadSafe (etc or any other constraints you liked). Then such constrained functions could only call other functions which had at least (or more) constraints. (eg a method maked ThreadSafe could only call another method marked ThreadSafe (unless the coder explicitly cast away that constraint).

Here is the article:
http://www.artima.com/cppsource/codefeatures.html

The cool concept I liked was that the constraints were enforced at compile time.

Martin York
Awesome. Sound like good company to be in.
Jherico
A: 

I don't think the core language, and especially the const keyword, would be the right place for this. The concept of const in C++ is meant to express the idea that a particular action will not modify a certain area of memory. It is a very low-level idea.

What you are proposing is a logical const-ness that has to do with the high-level semantics of your program. The main problem, as I see it, is that semantics can vary so much between different classes and different programs that there would be no way for there to be a one-size-fits all language construct for this.

What would need to happen is that the programmer would need to be able to write validation code that the compiler would run in order to check that particular operations met his definition of semantic (or "logical") const-ness. When you think about it, though, such code, if it ran at compile-time, would not be very different from a unit test.

Really what you want is for the compiler to test whether functions adhere to a particular semantic contract. That's what unit tests are for. So what you're asking is that there be a language feature that automatically runs unit tests for you during the compilation step. I think that's not terribly useful, given how complicated the system would need to be.

Tyler McHenry
Good answer. I think the same logic applies to the "mutable" keyword as well, but you have to draw the line somewhere, and that is where C++ drew it.
T.E.D.
The idea that const ensures that a bit of runtime code in C++ will not modify a certain area of memory is a new one on me, a very non-OO view, and demonstrably false. When you pass an object into a function that takes a const reference, all const does is say that 'the public state of this class will not change'. As far as I know it is and always has been a strictly compile time check. If you have something that says otherwise I'd be very interested to see it.
Jherico
+1  A: 
Michael E
+1 Not even that, but the constraint 'cannot change sort order' implies that there is a single sort order. With the language as is, you can use the same object as key in two different containers using different ordering methods (through the use of functors).
David Rodríguez - dribeas
Your argument against a new language feature is that the compiler as written can't support it?
Jherico
@dribeas: The new functionality would have to be integrated into the existing concept of a comparator. A comparator template could specify a specific sort criteria like const("sort_alphabetical"), with a pre-defined marker like const("_comparable") being used where sort order is unambiguous (as in the case of integers).
Jherico
A datatype cannot worry about external concerns like different sort programs. The best it can do is control it's public interface. Presumptuously, sort_alphabetical is dependent on operator< so what const("sort_alphabetical") is really saying is that operator< is invariant. The human name is irrelevant. Also, an ADT cannot possibly control everything about its "full" public interface since it cannot know about its offset in a vector or the exact time it was created. There are always external attributes that could affect a sort. You have to be careful of writing checks your ADT can't cash.
Samuel Danielson
@Jherico: not only can the current compiler not support it, I don't think that it is even possible for the compiler to support compile-time checking of arbitrary constraints in the general case. So my argument is a bit stronger than that.
Michael E
It doesn't strike me as any more difficult than matching a function signature.
Jherico
@Jherico but what does "MaintainsSortOrder" mean to the compiler? Is it just an arbitrary constraint name and you take the programmer's word that they actually maintain it? With const, the compiler can force them to maintain it. If you allow arbitrary constraints with the requirement of compile-time checking, you hit the halting problem and thus the theoretical "no, a compiler cannot check this" (not just "the current C++ compiler can't" -- no compiler can). I'd rather stick to what the compiler can verify.
Michael E
As I've said elsewhere, yes, its entirely up to the programmer to ensure that his functions obey the constraints they set out. It would be completely unreasonable to expect otherwise. The current 'const' behavior where the compiler checks to ensure all members are untouched is too constricting anyway, hence the existence of the mutable keyword.
Jherico
+1  A: 
Ben Strasser
Part of the design of C++ is that things you didn't write don't get magically inserted into your code. That includes virtually any kind of runtime checking. The purpose of this new kind of const is not to enforce bitwise accuracy, but to provide devs a tool and a compile time check that 'this function meets these constraints' where the constraint is more complex than 'this object is unchanged in any way'.
Jherico
+3  A: 

Refinement

When an ADT is indistinguishable from itself after some operation the const property holds for the entire ADT. You wish to define partial constness.

In your sort order example you are asserting that operator< of the ADT is invariant under some other operation on the ADT. Your ad-hoc const names such as "rotation" are defined by the set of operations for which the ADT is invariant. We could leave the invariant unnamed and just list the operations that are invariant inside const(). Due to overloading functions would need to be specified with their full declaration.

void set_color (Color c) const (operator<, std::string get_name());
void set_name  (std::string name) const (Color get_color());

So the const names can be seen as a formalism - their existence or absence doesn't change the power of the system. But 'typedef' could be used to name a list of invariants if that proves useful.

typedef const(operator<, std::string get_name()) DontWorryOnlyNameChanged;

It would be hard to think of good names for many cases.

Usefulness

The value in const is that the compiler can check it. This is a different kind of const.

But I see one big flaw in all of this. From your matrix example I might incorrectly infer that rotation and translation are independent and therefore commutative. But there is an obvious data dependency and matrix multiplication is not commutative. Interestingly, this is an example where partial constness is invariant under repeated application of one or the other but not both. 'translate' would be surprised to find that it's object had been translated due to a rotation after a previous translation. Perhaps I am misunderstanding the meaning of rotate and translate. But that's the problem, that constness now seems open to interpretation. So we need ... drum roll ... Logic.

Logic

It appears your proposal is analogous to dependent typing. With a powerful enough type system almost anything is provable at compile time. Your interest is in theorem provers and type theory, not C++. Look into intuitionistic logic, sequent calculus, Hoare logic, and Coq.

Now I've come full circle. Naming makes sense again,

int times_2(int n) const("divisible_by_3");

since divisible_by_3 is actually a type. Here's a prime number type in Qi. Welcome to the rabbit hole. And I pretended to be getting somewhere. What is this place? Why are there no clocks in here?

Samuel Danielson
You could be using a matrix to express a translation and rotation that are actually independent of each other (like a point cloud having a rotation about its center but being able to be translated separately), however, this kind of use begs the question, why not use a vector/quaternion pair instead of a matrix in the first place.
Jherico