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.