What additions would you like to see to the C++ language

Started by
145 comments, last by Polymorphic OOP 18 years, 9 months ago
Quote:Original post by Nemesis2k2
Quote:I don't feel the need for multi-level breaks because we have goto and return

I'd rather have multi-level break, and be one step closer to not needing goto at all personally.

And just what's wrong with goto in that context? I don't use it (by which I mean at all) but I think it's just as good as a multi-level break, if not better than Java's multi-level break.

Quote:In a similar vein, I think a "restart" keyword would be a valuble addition, which someone else suggested in a thread on this issue.


That sounds suspiciously like the "continue" keyword. What exactly does "restart" do?
Advertisement
Quote:And just what's wrong with goto in that context? I don't use it (by which I mean at all) but I think it's just as good as a multi-level break, if not better than Java's multi-level break.

Some people will argue there's nothing wrong with it. I personally believe however that high-level flow control should replace goto, and provide a safer, more intuitive environment. Using goto for a multi-level break I see as a legitimate use of goto in C++, which IMO means we're missing a higher-level construct to fill that role. Not everyone agrees on this however, as evidenced by the numerous debates on the use of goto and the merits of multi-level break. Personally, I would like to see the addition none the less. New features don't preclude the ability to do things the way they are done now, either using goto or by flags.

Quote:That sounds suspiciously like the "continue" keyword. What exactly does "restart" do?

Equivelant goto behaviour:

restart:for(int i = 0; i < something; ++i){	if(some condition)	{		goto restart;	}}
Oh I thought of another thing I want! Local function definitions. There's a lot of times when I want to repeat an operation, but the operation is so specific to the context and so small that I don't want to pull it out into a seperate function.

In the past, I've done this with locally-defined macros:

function clearAdjacentBlocks(int block_x, int block_y){  // clear blocks to the north, west, east, south  #define CLEAR_BLOCK(x,y)  \    if ((x) >= 0 && (x) < BOARD_WIDTH && (y) >= 0 && (y) < BOARD_HEIGHT) \     {     \       blocks[x][y].state = BLOCK_STATE_CLEARING;    \      blocks[x][y].clearTime = 1;    \    }     CLEAR_BLOCK(block_x, block_y);  CLEAR_BLOCK(block_x-1, block_y);  CLEAR_BLOCK(block_x+1, block_y);  CLEAR_BLOCK(block_x, block_y-1);  CLEAR_BLOCK(block_x, block_y+1);  #undef CLEAR_BLOCK}


and I have to say, I'm not proud of that code.
Concept/duck types. Types defined in terms of the operations which can be applied to their members. Obviously useful for template programming:

unary_function for_each(input_iterator first,                        input_iterator last,                        unary_function f);unary_function for_each(iteratable sequence,                        unary_function f);{ return for_each(sequence.begin(), sequence.end(), f);}


Now there could be no confusion as to what type of arguments you're allowed to send to for_each, and you can also declare a usefully typed generic two-argument version.

Anonymous functions.

boost::lambda just doesn't cut it.

for_each(list, lambda (item) { if (item == 1) return x;                               else if (item == 2) throw "blah blah"; } );


Obvious stuff.

Type inference via auto, typeof.

Fix the template system.

C++'s templates are broken in nasty ways.

No template typedefs means I can't do this:

template<typename type, int size> class Vector { ... }template<typename type> typedef Vector<type, 2> Vec2;template<typename type> typedef Vector<type, 3> Vec3;template<typename type> typedef Vector<type, 4> Vec4;


No 'metatyping' for type parameters:

template<typename type: Window>class SomeWindowRelatedClass{}


Namespaces.

Access specifiers for namespaces.

All named types should introduce namespaces, including enums.

Typed enums.

Enums should have a definable base type.

An enum should be able to 'include' another enum, to serve as an extension of it. The included enum cannot be treated as a 'base type', because in
enum Foo{  FRED, BARNEY};enum Bar: Foo{  WILMA, BETTY};

Bar is meant to be a subclass of Foo, and therefore any valid Bar should be a valid Foo. The only values of a valid Foo are FRED or BARNEY, so WILMA and BETTY could never be used. Effectively, Bar wishes to establish itself as a supertype of Foo.

Potentially, you could have Bar subclass Foo if it contained specialisations of the enumeration members:
enum Foo{  NUMBER,  STRING};enum Bar: Foo{  Foo::NUMBER = { FIXNUM, BIGNUM },  Foo::STRING = { ASCII, UNICODE },};

So here a Bar might be a FIXNUM, BIGNUM, ASCII or UNICODE. When viewed as a Foo, the options would be NUMBER or STRING. I am uncertain of the efficiency implications, or even general usefulness, of such a system.

Switch.

The implicit "fall-through" is confusing to new programmers.

I would favour using C#'s "goto case" statement in the general case [no pun intended], since that works even if you move the cases around, and reusing the existing "continue" statement to perform an explicit fall-through. An empty case statement might also be treated as an explicit fall-through: another option is to allow the case expression to be a comma-seperated list of options.

The compiler could then treat implicit fall-through as an error, if an appropriate option was selected.

Named blocks.

Contrary to popular belief, goto isn't named break: it's goto. Observe:

foo: for (int i = 0; i < 255; ++i)  for (int j = 0; j < 255; ++j)  {    if (x)      goto foo;    else if (y)      break foo;    else      continue foo;  }


It should be clear that the three commands are intended to do quite different things.

Whilst it's possible to modify the example so that all three control statements are gotos:

foo: for (int i = 0; i < 255; ++i){  for (int j = 0; j < 255; ++j)  {    if (x)      goto foo;    else if (y)      goto break_foo;    else      goto continue_foo;  }  continue_foo: ;}break_foo: ;


I don't think that's notably more usable. Since named blocks can't possibly change the behaviour of any existing programs, and since it is restricted to changing the syntax of two statements -- break and continue -- and since it isn't asking the compiler to calculate anything it wouldn't already know -- where to jump to do a break or continue from a particular block -- I can't see any valid argument against it.

Multiple dynamic dispatch.

class A {};class B: public A {};class C: public B {};class Foo {  void do_it (A * a) { cout << "A"; }  void do_it (B * b) { cout << "B"; }  void do_it (C * c) { cout << "C"; }};void blah (A * a){  Foo().do_it(a);}blah(new C);


This prints A. It should print C. Before you point out the painfully obvious, yes I know it prints A because it choose the method at compile time. That's exactly what's wrong.

Protection against object slicing.

class Foo{  virtual void do_it () { cout << x; }  int x;};class Bar: public Foo{  virtual void do_it () { cout << y; }  int y;};Foo foo = Bar();foo.do_it();


Either the language shouldn't permit Bar() to assigned to a Foo -- upcasting would only be allowed via pointers -- or it should display a warning: this program will never do what a novice programmer might expect it to do.

Opt-out not opt-in of advanced features.

The language should recommend garbage collection as the standard memory management technique -- to be explicitly disabled with a compiler option when it is not required. It should expose GC control features via such functions as std::gc::collect, std::gc::enable, std::gc::disable, and std::gc::usage_hint. It still needs to support explicit destruction of objects, whether via delete, or the automatic destruction of stack-allocated objects.

std::gc::enable/disable could be wrapped up in a library function in the style

{  std::gc::without_gc _ ();  // No GC occurs until the without_gc object is destroyed.}


The language should provide RTTI by default, again to be disabled by compiler option if it really isn't required.

Reflection.

C++ needs reflection. If RTTI is enabled, a set of functions in, say std::mop (for meta-object protocol, would provide information about objects: including classes and functions. The MOP would also allow for construction of objects representing argument lists, and the invocation of functions with such objects. This could allow for a robust and understandable library solution for dynamic dispatch.

Reflection doesn't need to be in the language, per se, unless we want to expose reflection to template metaprogramming, the compiler would just need to make sure it put the appropriate data in the output file where the library would know where to look for it.

An understanding that 'work-arounds' are the inherently the wrong solution.

Most things on this list, people will say you "you can work around that by dancing in circles waving a lime green turkey above your head whilst chanting the lyrics to Copacabana backwards", or something equally intelligible.

Labelling a solution as a "work around" is an admission that it's the wrong solution.

MaulingMonkey's work-around the lack of ability to instantiate classes by name is a lot of work, can only instantiate subclasses of a particular class -- so you're stuck if you want a factory which cross-cuts any hierarchies you might have -- and can't accept arbitrary argument lists to be passed to the constructor.

All of these things could perhaps be added with time, but the compiler already has all the information that the programmer would have to extract from it via template trickery. Why not have the compiler do the work?

It's obviously fine for inherently complex problems to have inherently complex solutions. C++ is explicitly a general purpose programming language, so specialised problems call for specialised libraries rather than support in the language or standard library.

But then there are common problems faced by many C++ programmers, which suggests those problems are general problems which therefore deserve general solutions.
Quote:Original post by Nemesis2k2
...

To each his own I guess then. I fail to see how a multilevel break is any more high level than a goto but *shrugs*.

Quote:
Quote:That sounds suspiciously like the "continue" keyword. What exactly does "restart" do?

Equivelant goto behaviour:

*** Source Snippet Removed ***


Well I suppose that wouldn't hurt either, although that's still quite doable without a goto:
for(int i = 0; i < something; ++i) {	if(some condition) {		i = 0; continue;	}}
All of the posts make it clear that what we need isn't C++. The language is extremely conservative in nature. This makes me want to design my own language just to learn all the techniques involved. It'd be cool to implement all of this stuff - reflection is extremely badass, and I cannot see a reason why a lot of it could be implemented at compile-time, where the compiler generates the needed code.

I'm kinda sick of all the new languages coming out with explicit political agendas. I shouldn't need a twenty meg runtime environment to support a 100k program.
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
Quote:Original post by MaulingMonkey
Well I suppose that wouldn't hurt either, although that's still quite doable without a goto:
for(int i = 0; i < something; ++i) {	if(some condition) {		i = 0; continue;	}}


But this means messing with a loop variable, which is apt to confuse people: it might not be obvious that you're restarting the loop. And supposing you later change the loop to start somewhere else (perhaps you find it's better to go in reverse), you'd have to change all the psuedo-restarts. On the other hand, the meaning of an explicit "restart" is clear, and it doesn't need to be updated if the loop initialiser is modified.
Quote:Well I suppose that wouldn't hurt either, although that's still quite doable without a goto:

Not as neat though, and it requires fiddling with the loop counter within a loop, which a lot of people will have an aversion to. It would also break if you construct an object in the initialization statement, and that object needs to be destroyed and re-created to iterate through again (although such a case is contrived).
Quote:Original post by Nemesis2k2
Quote:Well I suppose that wouldn't hurt either, although that's still quite doable without a goto:

Not as neat though, and it requires fiddling with the loop counter within a loop, which a lot of people will have an aversion to. It would also break if you construct an object in the initialization statement, and that object needs to be destroyed and re-created to iterate through again (although such a case is contrived).

Not that contrived. If the loop control variable isn't a copy-constructable type, you'll need to be doing something like:
for (ifstream i("foobar"); i.eof(); ){  int c = i.get();  ...  if (rescan_required)    restart;}

Now obviously it's possible to recast that snippet so that it doesn't need restart. The question is whether you believe you should change your code to fit the language, or the language should be flexible enough that you don't need to.
Quote:Original post by Nathan Baum
Named blocks.

Contrary to popular belief, goto isn't named break: it's goto.


But it can be used as a named break, which was my point.

Quote:I don't think that's notably more usable.


Question is, is that notably LESS usable. I'd argue no (my 2¢). Conversly (using boolean logic) I would also argue that a named break is no more noticably usable than a goto, and thus no benifit derived from including it within the language. This is all my opinion of course.

Quote:Since named blocks can't possibly change the behaviour of any existing programs, and since it is restricted to changing the syntax of two statements -- break and continue -- and since it isn't asking the compiler to calculate anything it wouldn't already know -- where to jump to do a break or continue from a particular block -- I can't see any valid argument against it.


The potential for time wasted standarding and implementing that feature rather than, say, your metatyping proposal, is a good argument in my opinion :-). But of course I wouldn't have problems with a named break/continue if you want to use your time implementing that in all existing compilers for me ;-).

Quote:Multiple dynamic dispatch.

Definate agree although not necessairly on the actual use/syntax example provided (such a change can and probably will break someone's existing code).

Quote:An understanding that 'work-arounds' are the inherently the wrong solution.

So you're saying your average newbie should in fact spend 10 years becoming a C++ expert and networking with the members of the standard's committe to influence the decision making based upon whatever language's flaw is preventing them from finishing "Hello World, Mk. 2"?

Workarounds are the inheritly right solution... for the average joe programmer. I agree with what I believe you mean, however, that from the standard implementors viewpoint, it is the wrong solution (in some of the examples anyways). I'm simply contrasting the literal and (believed) intended meanings for clarity rather than pondering an actual question above :-).

Quote:Most things on this list, people will say you "you can work around that by dancing in circles waving a lime green turkey above your head whilst chanting the lyrics to Copacabana backwards", or something equally intelligible.


Some of it is quite intelligible and in fact easy.

Quote:MaulingMonkey's work-around the lack of ability to instantiate classes by name is a lot of work,

Written a million times? Yes. Once? No. Add it to your library, that's why it's templatized :-).
Quote:can only instantiate subclasses of a particular class -- so you're stuck if you want a factory which cross-cuts any hierarchies you might have -- and can't accept arbitrary argument lists to be passed to the constructor.

Wrong, although the use of "base_t" as a template parameter is misleading in this regards. If you want a cross-cutting hierarchy, simply pass void as the "base" type. There's no real better solution: considering first and foremost (my 2¢) that C++ is a statically typed language, we must rule out having different return types depending on the argument which may only be determinable at run time, so it is obvious we must have some sort of base pointer type. I provide the base argument so that one is not forced to upcast when dealing with a single heiarchy, as that's just plain ugly and brittle (my 2¢ again), but for a cross-heiarchy group, what would we use? They don't share a common base (i.e. "Object"), leaving, well, nothing (otherwise known as void).

Quote:Nathan Baum
But this means messing with a loop variable, which is apt to confuse people: it might not be obvious that you're restarting the loop.

Easily fixable with: //<--- I'm restarting the loop.
I'm all for self documenting code, but if it's not that obvious then it deserves a comment.

I've never had an issue with such code, but I'll admit this is because the concept of restarting a loop is fairly alien to me, a sign of something wrong with my design rather than the best solution (seems to much like restarting my computer just because I installed new software, I guess ;-)).

This topic is closed to new replies.

Advertisement