134

I have recently been learning D and am starting to get some sort of familiarity with the language. I know what it offers, I don't yet know how to use everything, and I don't know much about D idioms and so on, but I am learning.

I like D. It is a nice language, being, in some sort of ways, a huge update to C, and done nicely. None of the features seem that "bolted on", but actually quite well thought-out and well-designed.

You will often hear that D is what C++ should have been (I leave the question whether or not that is true to each and everyone to decide themselves in order to avoid unnecessary flame wars). I have also heard from several C++ programmers that they enjoy D much more than C++.

Myself, while I know C, I can not say that I know C++. I would like to hear from someone knowing both C++ and D if they think there is something that C++ does better than D as a language (meaning not the usual "it has more third-party libraries" or "there are more resources" or "more jobs requiring C++ than D exists").

D was designed by some very skilled C++ programmers (Walter Bright and Andrei Alexandrescu, with the help of the D community) to fix many of the issues that C++ had, but was there something that actually didn't get better after all? Something he missed? Something you think wasn't a better solution?

Also, note that I am talking about D 2.0, not D 1.0.

Adam Lear
  • 31,939
  • 8
  • 101
  • 125
Anto
  • 11,157
  • 13
  • 67
  • 103
  • 15
    I made sure the D community does see this as I'm sure there are far more C++ devs than D devs around here. That way you'll have more interesting or at least varied answers. – Klaim Jul 30 '11 at 09:44
  • 7
    Also, D2 was designed by Walter Bright but with Alexandrescu too. You might want to fix that in your question. – Klaim Jul 30 '11 at 10:44
  • whether it's D or C++, a compiled language with that much high level stuff is never going to be a pretty language. I don't think C++ has that many issues though, it's just a matter of philosophy about what a compiled language should be. Backward compatibility with C for example is irrelevant today. – jokoon Jul 30 '11 at 18:34
  • 2
    @Klaim: there was (and still is) a lot of community involvement in D and standard library too. – Michal Minich Jul 30 '11 at 22:34
  • 28
    @Anto As a language, C++ is a lot better than D in making you, the programmer, hate your life. – Arlen Jul 30 '11 at 23:47
  • 2
    @jokoon, Haskell is a compiled language, and it's a very pretty language... Maybe you mean a language that supports low-level and high-level code simultaneously? – Peaker Jul 31 '11 at 14:54
  • @peaker: yes. D is pretty, but one reason I think it can't be adopted is backward compatibility with C-API stuff. I mean can D use existing C libraries ? – jokoon Jul 31 '11 at 16:41
  • 6
    @jokoon: Actually, yes, with very little work: http://www.digitalmars.com/d/2.0/interfaceToC.html – Anto Jul 31 '11 at 17:31

8 Answers8

132

When I joined D development I was in the peculiar position of being one of the people who know most there is to know about C++. Now I'm in the even more peculiar position to also be one of the people who know most there is to know about D. I'm not saying this to appropriate credit or bragging rights as much as to remark I'm in a curiously advantaged position to reply to this question. The same applies to Walter.

By and large, asking what C++ (and by that I mean C++2011) does better than D is as self-contradictory as the question, "If you pay a professional to clean your house, what are the places they'll leave dirtier than before?" Whatever of value was that C++ could do that D couldn't, it has always stood like a sore thumb to me and Walter, so almost by definition there's nothing C++ can ever do that's not within D's reach.

One thing that is seldom understood in language design (because few people have the luck to actually do some) is that there are much fewer unforced errors than it may appear. Many of us language users look at some construct or another and say, "Ew! This is so wrong! What were they thinking?" The fact of the matter is that most awkward instances in a language are aftermath of a few fundamental decisions that are all sound and desirable but are fundamentally competing or contradicting each other (e.g. modularity and efficiency, terseness and control etc).

With all this in mind, I'll enumerate a few things that I can think of, and for each I'll explain how D's choice derives from a desire to fulfill some other, higher-level, charter.

  1. D assumes all objects are movable by bitwise copy. This leaves a minority of designs to C++, specifically those that use internal pointers, i.e. a class containing pointers inside itself. (Any such design can be translated at no or negligible efficiency cost into D, but there would be a translation effort involved.) We made this decision to greatly simplify the language, make object copying more efficient with no or minimal user intervention, and avoid the entire copy construction morass and the rvalue references feature altogether.

  2. D disallows ambiguous-gender types (that can't decide whether they're value or reference types). Such designs are unanimously shunned in C++ and almost always wrong, but some of them are technically correct. We made this choice because it disallows mostly incorrect code and only a tiny fraction correct code that can be redesigned. We believe it is a good tradeoff.

  3. D disallows multi-root hierarchies. A previous poster here got very excited about this particular topic, but this is well-trodden ground and there is no palpable advantage of rootless hierarchies over hierarchies that all have a common root.

  4. In D you can't throw e.g. an int. You must throw an object inheriting Throwable. No contest the state of affairs is better in D, but, well, it's one thing C++ can do that D can't.

  5. In C++ the unit of encapsulation is a class. In D it's a module (i.e. file). Walter made this decision for two reasons: to naturally map encapsulation to file system protection semantics, and to obviate the need for "friend". This choice integrates very well within D's overall modularity design. It would be possible to change things to be more like C++, but that would force things; C++'s encapsulation scope choices are good only for C++'s physical design.

There could be one or two smaller things, but overall this should be it.

Andrei Alexandrescu
  • 1,233
  • 1
  • 7
  • 5
  • Classes that point inside themselves aren't the only designs, you'd also break designs where one class keeps a list of instances of another class, and it would be doubly bad if I only wanted some of them. For example, in a graphics rendering system, I might wish to keep a list of pointers to objects which are not currently flagged as invisible. Move my object without telling me and I'm left with a dud pointer. – DeadMG Jul 30 '11 at 15:12
  • 6
    @DeadMG: For that to work in C++, the object being moved would need a back pointer to the object pointing to it (so that it could update during copy construction). If that's the case, in D you can use the postblit constructor to update the pointer anyway. Please do not argue against D if you only have a passing knowledge of it. – Peter Alexander Jul 30 '11 at 15:35
  • @Peter: In that case, how is a postblit constructor any different to a move constructor? – DeadMG Jul 30 '11 at 16:03
  • 1
    @DeadMG: A postblit constructor is run on each object after it is copied (essentially using memcpy). The postblit constructor knows nothing about where it was copied/moved from (it has no args). It just adjusts itself after the copy (e.g. a D struct version of std::vector would allocate a new buffer and copy its previous values into the new buffer before adjusting its internal pointer). – Peter Alexander Jul 30 '11 at 16:09
  • 1
    @Peter: I decided to look up more about postblit constructors, and I'm looking at this page: http://www.digitalmars.com/d/2.0/struct.html#StructPostblit. See, if you don't get a reference to the object that you moved from, how are you supposed to move it's internal state so that it doesn't double delete it? The page only shows copying semantics, not moving semantics. What if I had a named mutex or pipe or something that can't be copied? Or had an expensive copy facility? – DeadMG Jul 30 '11 at 16:25
  • 1
    @DeadMG: If you move then you just do the memcpy and don't delete the source and don't do the postblit. – Peter Alexander Jul 30 '11 at 16:29
  • @Peter: Right. Then how do I notify my observer that my object has moved? – DeadMG Jul 30 '11 at 16:33
  • 1
    @DeadMG: Ah yes, I see what you mean. In that case you are using the wrong tool for the job. I apologise for causing this confusion. Simply put, a value type (struct) simply shouldn't have things pointing to it (it's a value after all, not an object). It should be a reference type so that it has the infinite lifetime model and you can safely point to it. Value types are for values. If you want references to something then it should be a reference type. – Peter Alexander Jul 30 '11 at 16:44
  • 13
    @Peter: It should be a reference even though it's lifetime is strictly scope-based? I should waste the overhead of dynamically allocating it and the indirection and cache and collection overheads because I want to alias it? Also, I hope that the collector can collect it deterministically, for equivalent semantics. That is quite clearly not being in control. – DeadMG Jul 30 '11 at 17:07
  • 1
    Multi-rooted object hierarchies can be the natural outcome of combining two libraries that are independent from each other. I know a single exception base class from C# and I don't like it. Different software projects have different requirements on what data an exception class should transfer. IMO it's good to settle on one base class for a project, but not on one base class for the whole language. – sbi Jul 31 '11 at 01:04
  • 3
    @sbi: The existence of a top class does not affect your choices at all. In the class type lattice, there is always a top and a bottom. The bottom is only explicit in [a few languages](http://en.wikipedia.org/wiki/Bottom_type). The top (i.e. Object etc.) is explicit in more languages. These types always exist in concept; when they are also accessible, they simply offer a few extra facilities to the language's user without making trouble. – Andrei Alexandrescu Jul 31 '11 at 01:15
  • @Andrei: Um, am I understanding this right - D has an _Object_ base class? – sbi Jul 31 '11 at 01:30
  • 1
    @sbi: Objective-C makes wonderful use of a base class called NSObject. It is not, as such, a part of the language, but it may just as well be. Reference counting is done in a standard way. Combined with dynamism of Objective-C, having NSObject as a base class allows one to add a function to every other class in a program which, if used sparingly, can be quite helpful. (Example functions might be: 'if I conform to NSCoding protocol, return base64-encoded serialization; if not, return nil'.) I never looked into D, but any language with a base class is nowadays good in my eyes. – Ivan Vučica Jul 31 '11 at 10:40
  • 1
    @Andrei What is the state of numerical libraries in D? Is there a D implementation of FFT, BLAS or LAPACK (or equivalent functionality)? Can D be more efficient in numerical computations than Java? – quant_dev Jul 31 '11 at 16:20
  • 3
    @quant_dev I remember a talk from the first D conference where an Australian physicist compiled his FFT code in both Fortran and D (gdc at the time, iirc, and using D's templates), and they had nearly identical output from the compiler when when he was looking at each instruction (though D's syntax 'c = a + b;' was much easier to program). This anecdote is several years old at this point, but worth keeping in mind as a starting point for your own benchmarking. – Sean Jul 31 '11 at 23:39
  • @Sean D has primitive types, I see: http://www.digitalmars.com/d/2.0/ctod.html#types So not everything is derived from Object ;-) – quant_dev Aug 01 '11 at 09:38
  • @sbi: Object is the base of all class object and offers a few fundamental methods: toString, toHash, opCmp, opHash, and static factory. See http://d-programming-language.org/phobos/object.html – Andrei Alexandrescu Aug 01 '11 at 14:08
  • 6
    @quant_dev: you'll be glad to hear there's a GSoC project already in a good shape focused on high performance linear algebra using BLAS. It also provides "naive" implementations of the appropriate primitives for testing and benchmarking purposes. To answer your second question, Java sets the bar quite low for comparing numeric libraries. It will always have the trouble of going over a JNI barrier for accessing high-performance algebra libs, and the syntax will be bad because Java lacks operator overloading. – Andrei Alexandrescu Aug 01 '11 at 15:00
  • @Andrei Alexandrescu How does D compare with C# in numerical computing? – quant_dev Aug 01 '11 at 15:01
  • One thing that annoys me (but not worth an answer) is the lack of nested modules (such as what Python does with __init__.py). I understand the design decision, but it is a very missed feature and makes compatibility with RPC libraries harder/impossible unless they also limit their definitions to not allow a module within a module. – jsternberg Aug 03 '11 at 21:50
  • 4
    @PeterAlexander: DeadMG is right on the point. "You shouldn't use pointers to value types" is clearly ignorant of the fact that pointers in any language are generally used with value types (do you really expect to see an `Object*` as widely used as an `int*`?) and D seems to be completely ignoring the performance penalty, or claiming it doesn't exist. That's obviously false -- the cache miss is quite noticeable in many cases, so C++ will always have that flexibility advantage over D. – user541686 Dec 03 '11 at 08:32
  • @Mehrdad: Let's be clear here: if you want to, you can use pointers in D just like you can in C++ and they have the same performance. Also, I am unaware of any performance penalty using references vs pointers. The only thing I can think of is v-calls, but that has nothing to do with DeadMG's problem and can be easily avoided by using final class. Finally, DeadMG's problem only occurs in this strange situation where you have value types that are moving around in memory and need to inform a manager object. If you are moving lots of objects around and worrying about a v-call then you have issues. – Peter Alexander Dec 03 '11 at 10:16
  • 2
    @PeterAlexander: Yes, let's be clear indeed -- I'm talking about DeadMG's last comment: *I should waste the overhead of dynamically allocating it and the indirection and cache and collection overheads because I want to alias it?* I was saying that *forcing the user to use reference types* creates a performance penalty due to the heap allocation, whereas pointers to structs don't have that penalty because the struct can be allocated on the stack. This is clearly something that C++ does better than D (even better since it allows value type inheritance), as it provides more control/performance. – user541686 Dec 03 '11 at 10:27
  • @Mehrdad: You don't have to heap allocate it if you don't want. You can use emplace to put the object wherever you like. The indirection happens for pointers too. There's no difference in cache if you put it where you want it and there's no collection overhead if you free it manually. – Peter Alexander Dec 03 '11 at 10:48
  • @PeterAlexander: Yes, the exact problem with `emplace` is that it requires a ridiculous amount of typing to do something so simple -- first I have to declare a `ubyte` buffer, then I have to do `sizeof` on the type (which may need something like `typeof(foo).sizeof`) to get the size of the buffer, then I need to `emplace` it (God knows the hell I'd go through if the type had alignment > 16 bytes). So while it works in theory, in practice it's been made to prevent people from actually using it, given how painful it is to use. It's nothing like in C++. – user541686 Dec 03 '11 at 11:00
  • @Mehrdad: Wrap it in a function. – Peter Alexander Dec 03 '11 at 11:12
  • 2
    @PeterAlexander: You must be joking, right? How do I access the caller's stack frame and allocate data on it? – user541686 Dec 03 '11 at 11:15
  • @Mehrdad: Pass in a pointer to it like you would in C++ with placement new? Either that or write a mixin. You're really blowing this out of proportion for something that is very rare anyway. – Peter Alexander Dec 03 '11 at 11:48
  • 3
    @PeterAlexander: In C++ I would just allocate it on the stack: `MyClass s;`, I don't see why you think we need placement new. And I do it **very often** (don't you??); it's not "rare" at all. In D I *used* to be able to say `scope auto foo = new MyClass();`, but since they removed it (probably because they don't want us to use it!) now I have to use `emplace`, which is painful. So effectively, D doesn't let you do what C++ does: to allocate an object on the stack. It's quite a fair comparison, and C++ does vastly better than in this regard. There's nothing "out of proportion" here. – user541686 Dec 03 '11 at 20:02
124

Most of the things C++ "does" better than D are meta things: C++ has better compilers, better tools, more mature libraries, more bindings, more experts, more tutorials etc. Basically it has more and better of all the external things that you would expect from a more mature language. This is inarguable.

As for the language itself, there are a few things that C++ does better than D in my opinion. There's probably more, but here's a few that I can list off the top of my head:

C++ has a better thought out type system
There are quite a few problems with the type system in D at the moment, which appear to be oversights in the design. For example, it is currently impossible to copy a const struct to a non-const struct if the struct contains class object references or pointers due to the transitivity of const and the way postblit constructors work on value types. Andrei says he knows how to solve this, but didn't give any details. The problem is certainly fixable (introducing C++-style copy constructors would be one fix), but it is a major problem in language at present.

Another problem that has bugged me is the lack of logical const (i.e. no mutable like in C++). This is great for writing thread-safe code, but makes it difficult (impossible?) to do lazy intialisation within const objects (think of a const 'get' function which constructs and caches the returned value on first call).

Finally, given these existing problems, I'm worried about how the rest of the type system (pure, shared, etc.) will interact with everything else in the language once they are put to use. The standard library (Phobos) currently makes very little use of D's advanced type system, so I think it is reasonable the question whether it will hold up under stress. I am skeptical, but optimistic.

Note that C++ has some type system warts (e.g. non-transitive const, requiring iterator as well as const_iterator) that make it quite ugly, but while C++'s type system is a little wrong at parts, it doesn't stop you from getting work done like D's sometimes does.

Edit: To clarify, I believe that C++ has a better thought out type system -- not necessarily a better one -- if that makes sense. Essentially, in D I feel that there is a risk involved in using all aspects of its type system that isn't present in C++.

D is sometimes a little too convenient
One criticism that you often hear of C++ is that it hides some low-level issues from you e.g. simple assignments like a = b; could be doing many things like calling conversion operators, calling overload assignment operators etc., which can be difficult to see from the code. Some people like this, some people don't. Either way, in D it is worse (better?) due to things like opDispatch, @property, opApply, lazy which have the potential to change innocent looking code into things that you don't expect.

I don't think this is a big issue personally, but some might find this off-putting.

D requires garbage-collection
This could be seen as controversial because it is possible to run D without the GC. However, just because it is possible doesn't mean it is practical. Without a GC, you lose a lot of D's features, and using the standard library would be like walking in a minefield (who knows which functions allocate memory?). Personally, I think it is totally impractical to use D without a GC, and if you aren't a fan of GCs (like I am) then this can be quite off-putting.

Naive array definitions in D allocate memory
This is a pet peeve of mine:

int[3] a = [1, 2, 3]; // in D, this allocates then copies
int a[3] = {1, 2, 3}; // in C++, this doesn't allocate

Apparently, to avoid the allocation in D, you must do:

static const int[3] staticA = [1, 2, 3]; // in data segment
int[3] a = staticA; // non-allocating copy

These little 'behind your back' allocations are good examples of my previous two points.

Edit: Note that this is a known issue that is being worked on.
Edit: This is now fixed. No allocation takes place.

Conclusion
I've focussed on the negatives of D vs C++ because that's what the question asked, but please don't see this post as a statement that C++ is better than D. I could easily make a larger post of places where D is better than C++. It's up to you to make the decision of which one to use.

Peter Alexander
  • 2,199
  • 2
  • 14
  • 13
  • I looked at D a few years ago (before 2.0). Garbage collection wasn't really required then - it was there by default, but you could opt out for low level code. The thing I thought was wrong about this is that I could find no way to opt back in. For example, in a tree based container, the library code can manage memory for the tree nodes itself. The tree as a whole can still be collectable, with IIRC a destructor that collects all those nodes. But objects referenced by the data in that container should be collectible too - there should be a hook to marks all data items in the tree for the GC. –  Aug 01 '11 at 13:46
  • 3
    You can still disable the GC for low-level code - Peter is saying that the language presently depends a lot on it. Also, you can tell the GC to scan ranges outside of its managed heap using its API: [GC.addRange from core.memory](http://d-programming-language.org/phobos/core_memory.html#addRange). – Vladimir Panteleev Aug 02 '11 at 00:48
  • +1 for pointing out that the D standard library is garbage collected and that GC-off code is not a seamless interop. It's not something I've thought about it, but it does seem like a major hurdle to overcome. – masonk Aug 16 '13 at 13:38
65

I think that you're going to have a very hard time finding much in D which is objectively worse than C++. Most of the issues with D where you could objectively say it's worse are either quality of implementation issues (which are generally due to how young the language and implementation are and have been being fixed at a breakneck speed of late), or they're issues with a lack of 3rd party libraries (which will come with time). The language itself is generally better than C++, and the cases where C++, as a language, is better are generally either going to be where there's a tradeoff where C++ went one way and D went another, or where someone has subjective reasons for why they think that one is better than another. But the number of outright objective reasons why C++, as a language, is better are likely to be few and far between.

In fact, I have to really wrack my brain to come up with reasons why C++, as a language, is better than D. What generally comes to mind is a matter of tradeoffs.

  1. Because D's const is transitive, and because the language has immutable, it has far stronger guarantees than C++'s const, which means that D does not and cannot have mutable. It cannot have logical const. So, you get a huge gain with D's const system, but in some situations, you just can't use const like you would have in C++.

  2. D has only one cast operator, whereas C++ has 4 (5 if you count the C cast operator). This makes dealing with casts in D easier in the general case, but is problematic when you actually want the extra complications/benefits that const_cast and its brethren provide. But D is actually powerful enough that you could use templates to implement C++'s casts, so if you really want them, you can have them (and they may even end up in D's standard library at some point).

  3. D has far fewer implicit casts than C++ and is far more likely to declare that two functions are in conflict with one another (forcing you to be more specific about which of the functions that you mean - either with casts or by giving the full module path). At times, that can be annoying, but it prevents all kinds of function hijacking issues. You know that you're really calling the function that you mean to.

  4. D's module system is far cleaner than C++'s #includes (not to mention, way faster at compiling), but it lacks any kind of namespacing beyond the modules themselves. So, if you want a namespace within a module, you have to go the Java route and use static functions on a class or struct. It works, but if you really want namespacing, it's obviously not as clean as real namespacing. For most situations, however, the namespacing that the modules themselves provide you is plenty (and quite sophisticated when it comes to stuff like conflicts actually).

  5. Like Java and C#, D has single inheritance rather than multiple inheritance, but unlike Java and C#, it gives you some fantastic ways to get the same effect without all of the problems that C++'s multiple inheritance has (and C++'s multiple inheritance can get very messy at times). Not only does D have interfaces, but it has string mixins, template mixins, and alias this. So, the ultimate result is arguably more powerful and does not have all of the issues that C++'s multiple inheritance does.

  6. Similar to C#, D separates structs and classes. Classes are reference types which have inheritance and are derived from Object, whereas structs are value types without inheritance. This separation can be both good and bad. It gets rid of the classic slicing problem in C++ and it helps separate types which are really value types from those which are supposed to be polymorphic, but at first, at least, the distinction might be annoying to a C++ programmer. Ultimately, there are a number of benefits to it, but it does force you to deal with your types somewhat differently.

  7. Member functions of classes are polymorphic by default. You can't declare them non-virtual. It's up to the compiler to decide if they can be (which really is only the case if they're final and aren't overriding a function from a base class). So, that could be a performance problem in some cases. However, if you really don't need the polymorphism, then all you have to do is use structs, and it's not an issue.

  8. D has a built-in garbage collector. Many from C++ would consider that to be a serious downside, and truth be told, at present, its implementation could use some serious work. It has been improving, but it's definitely not on-par with Java's garbage collector yet. However, this is mitigated by two factors. One, if you're primarily using structs and other data types on the stack, then it's not a big issue. If your program is not constantly allocating and deallocating stuff on the heap, it'll be fine. And two, you can skip the garbage collector if you want to and just use C's malloc and free. There are some language features (such as array slicing) which you'll have to avoid or be careful with, and some of the standard library isn't really usable without at least some use of the GC (particularly string processing), but you can write in D without using the garbage collector if you really want to. The smart thing to do is probably to generally use it and then avoid it where profiling shows that it's causing problems for performance critical code, but you can avoid it completely if you want to. And the quality of the GC's implementation will improve over time, eliminating many of the concerns that using a GC may cause. So, ultimately, the GC won't be as big a problem, and unlike Java, you can avoid it if you want to.

There are probably others as well, but that's what I can come up with at the moment. And if you'll notice, they're all tradeoffs. D chose to do some things differently than C++ that have definite advantages over how C++ does them but also have some disadvantages. Which is better depends on what you're doing, and in many cases will probably only seem worse at first and then you won't have a problem with it once you get used to it. If anything, the problems in D are generally going to be new ones caused by new stuff that other languages haven't done before or haven't done in quite the way that D has. Overall, D has learned very well from C++'s mistakes.

And D, as a language, improves over C++ in so many ways that I think that it's generally the case that D is objectively better.

  1. D has conditional compilation. This is one of the features that I frequently miss when I'm programming in C++. If C++ would add it, then C++ would improve by leaps and bounds when it comes to stuff like templates.

  2. D has compile-time reflection.

  3. Variables are thread-local by default but can be shared if you want them to be. This makes dealing with threads far cleaner than in C++. You're in complete control. You can use immutable and message passing to communicate between threads, or you can make variables shared and do it the C++ way with mutexes and condition variables. Even that is improved over C++ with the introduction of synchronized (similar to C# and Java). So, D's threading situation is far better than C++'s.

  4. D's templates are far more powerful than C++'s templates, allowing you to do far more, far more easily. And with the addition of template constraints, the error messages are way better than they are in C++. D makes templates very powerful and usable. It's no coincidence that the author of Modern C++ Design is one of D's main collaborators. I find C++ templates to be seriously lacking in comparison to D templates, and it can be very frustrating at times when programming in C++.

  5. D has built-in contract programming.

  6. D has a built-in unit test framework.

  7. D has built-in support for unicode with string (UTF-8), wstring (UTF-16), and dstring (UTF-32). It makes it easy to deal with unicode. And if you want to just use string and generally not worry about unicode, you can - though some understanding of the basics of unicode does help with some of the standard library functions.

  8. D's operator overloading is much nicer than C++'s, allowing you to use one function to overload multiple operators at the same time. A prime example of this is when you need to overload the basic arithmetic operators and their implementations are identical save for the operator. String mixins make it a breeze, allowing you to have one, simple function definition for them all.

  9. D's arrays are vastly better than C++'s arrays. Not only are they a proper type with a length, but they can be appended to and resized. Concatenating them is easy. And best of all, they have slicing. And that's a huge boon for efficient array processing. Strings are arrays of characters in D, and it's not a problem (in fact it's great!), because D's arrays are so powerful.

I could go on and on. A lot of the improvements that D provides are little things (like using this for constructor names or disallowing if statements or loop bodies where a semicolon is their entire body), but some of them are pretty large, and when you add it all together, it makes for a much better programming experience. C++ 0x does add some of the features that D has which C++ was missing (e.g. auto and lambdas), but even with all of its improvements, there still isn't going to be much which is objectively better about C++ as a language than D.

There's no question that there are plenty of subjective reasons to like one over the other, and the relative immaturity of D's implementation can be a problem at times (though it has been improving very quickly of late - especially since the repositories were moved to github), and the lack of 3rd party libraries can definitely be a problem (though the fact that D can easily call C functions - and to a lesser extent, C++ functions - definitely mitigates the problem). But those are quality of implementation issues rather than issues with the language itself. And as the quality of implementation issues are fixed, it will become that much more pleasant to use D.

So, I suppose that the short answer to this question is "very little." D, as a language, is generally superior to C++.

Jonathan M Davis
  • 1,435
  • 10
  • 9
  • 2
    Garbage collection languages use 2-5x more memory than non GC langs(according to Alexandrescu's talk on YT) so that is definitely a problem if that(mem usage) is the bottleneck. – NoSenseEtAl Aug 19 '11 at 09:10
9

RAII and stack memory usage

D 2.0 doesn't allow RAII to happen on the stack because it removed the value of the scope keyword in allocating class instances on the stack.

You can't do value-type inheritance in D, so effectively that forces you to do a heap allocation for any form of RAII.
That is, unless you use emplace, but that's very painful to use, since you have to allocate the memory by hand. (I have yet to find it practical to use emplace in D.)

user541686
  • 8,074
  • 8
  • 38
  • 49
6

C++ is much better at forcing you to be verbose. This might be better or worse in your eyes, depending on whether you like inference or verbosity.

Compare run-time memoization in C++:

template <typename ReturnType, typename... Args>
function<ReturnType (Args...)> memoize(function<ReturnType (Args...)> func)
{
    map<tuple<Args...>, ReturnType> cache;
    return ([=](Args... args) mutable {
            tuple<Args...> t(args...);
            return cache.find(t) == cache.end()
                ? cache[t] : cache[t] = func(args...);
    });
}

with the same thing in D:

auto memoize(F)(F func)
{
    alias ParameterTypeTuple!F Args;
    ReturnType!F[Tuple!Args] cache;
    return (Args args)
    {
        auto key = tuple(args);
        return key in cache ? cache[key] : (cache[key] = func(args));
    };
}

Notice, for example, the extra verbosity with template <typename ReturnType, typename... Args> versus (F), Args... versus Args, args... versus args, etc.
For better or worse, C++ is more verbose.

Of course, you could also do this in D:

template memoize(Return, Args...)
{
    Return delegate(Args) memoize(Return delegate(Args) func)
    {
        Return[Tuple!Args] cache;
        return delegate(Args args)
        {
            auto key = tuple(args);
            return key in cache ? cache[key] : (cache[key] = func(args));
        };
    }
}

and they would look almost identical, but then this would require a delegate, whereas the original accepted any callable object. (The C++0x version requires a std::function object, so either way, it's more verbose and restrictive in its inputs... which could be good if you like verbosity, bad if you don't.)

user541686
  • 8,074
  • 8
  • 38
  • 49
2

I don't know much about D, but many, many C++ programmers I know greatly dislike it, and I personally have to agree- I don't like the look of D and will not be taking a closer one.

In order to understand why D isn't gaining more traction, you need to start by understanding what attracts people to C++. In a word, the number one reason is control. When you program in C++, then you have complete control over your program. Want to replace the Standard library? You can. Want to do unsafe pointer casts? You can. Want to violate const-correctness? You can. Want to replace the memory allocator? You can. Want to copy around raw memory without regard for it's type? If you really want to. Want to inherit from multiple implementations? It's your funeral. Hell, you can even get garbage collection libraries, like the Boehm collector. Then you have issues like performance, which closely follows control- the more control a programmer has, the more optimized he can make his program. Performance is one of the major reasons to continue using C++.

Here's a few things that I've seen when doing a little research and speaking to a couple of people who have tried it:

Unified type hierarchy. C++ users use inheritance very rarely, most C++ programmers prefer composition, and types should only be linked through inheritance if there is a very good reason for doing so. The concept of Object violates this principle strongly by linking every type. In addition, it's violating one of C++'s most basic principles- you only use what you want. Not being given a choice about inheriting from Object, and the costs that go along with it, are very strongly against what C++ stands for as a language in terms of giving the programmer control over his program.

I've heard about problems with functions and delegates. Apparently, D has both functions and delegates as run-time callable function types, and they're not the same but they are interchangable or... something? My friend had quite a few problems with them. This is definitely a downgrade from C++, which just has std::function and you're done.

Then you've got compatibility. D is not particularly compatible with C++. I mean, no language is compatible with C++, let's face it, except C++/CLI which is kind of cheating, but as a barrier to entry, it's got to be mentioned.

Then, there are some other things. For example, just read the Wikipedia entry.

import std.metastrings;
pragma(msg, Format!("7! = %s", fact_7));
pragma(msg, Format!("9! = %s", fact_9));

printf is one of the most un-safe functions ever devised, in the same family as big problems like gets from the old C Standard library. If you search for it on Stack Overflow, you will find many, many questions relating to it's mis-use. Fundamentally, printf is a violation of DRY - you're giving the type in the format string, and then giving it again when you give it an argument. A violation of DRY where if you get it wrong, then very bad things happen- say, if you changed a typedef from a 16-bit integer to a 32-bit one. It's also not extendable at all- imagine what would happen if everyone invented their own format specifiers. C++'s iostreams may be slow, and their choice of operator may not be the greatest, and their interface could use work, but they are fundamentally guaranteed to be safe, and DRY is not violated, and they can be readily extended. This is not something that can be said of printf.

No multiple inheritance. That's very not the C++ way. C++ programmers expect to have complete control over their program and the language enforcing what you cannot inherit from is a violation of that principle. In addition, it makes inheritance (even more) fragile, because if you change a type from an interface to a class because you want to provide a default implementation or something, suddenly all your user's code is broken. That's not a good thing.

Another example is string and wstring. In C++ it's already quite painful to have to convert between them, and does this library support Unicode, and this old C library only uses const char*, and having to write different versions of the same function depending on the string argument type you want. Notably, the Windows headers, for example, have some extremely irritating macros to cope with the problem that can often interfere with your own code. Adding dstring to the mix is only going to make things worse, as now instead of two string types, you have to manage three. Having more than one string type is going to increase maintenance pains and introduce repetitive code dealing with strings.

Scott Meyers writes:

D is a programming language built to help programmers address the challenges of modern software development. It does so by fostering modules interconnected through precise interfaces, a federation of tightly integrated programming paradigms, language-enforced thread isolation, modular type safety, an efficient memory model, and more.

Language-enforced thread isolation is not a plus. C++ programmers expect full control over their programs, and the language forcing something is definitely not what the doctor ordered.

I'm also going to mention compile-time string manipulation. D has the ability to interpret D code at compile-time. This is not a plus. Consider the massive headaches caused by C's relatively limited preprocessor, well-known by all veteran C++ programmers, and then imagine how badly this feature is going to be abused. The ability to create D code at compile-time is great, but it should be semantic, not syntactic.

In addition, you can expect a certain reflex. D has garbage collection, which C++ programmers will associate with languages like Java and C# which are fairly directly opposed to it in philosophies, and the syntactic similarities will bring them to mind too. This isn't necessarily objectively justifiable, but it's something that certainly should be noted.

Fundamentally, it doesn't offer that much that C++ programmers can't already do. Maybe it's easier to write a factorial metaprogram in D, but we can already write factorial metaprograms in C++. Maybe in D you can write a compile-time ray-tracer, but nobody really wants to do that anyway. Compared to the fundamental violations of C++ philosophy, what you can do in D is not particularly notable.

Even if these things are only problems on the surface, then I'm pretty sure that the fact that on the surface, D doesn't actually look like C++ at all is probably a good reason that many C++ programmers are not migrating to D. Perhaps D needs to do a better job advertising itself.

Peter Mortensen
  • 1,050
  • 2
  • 12
  • 14
DeadMG
  • 36,794
  • 8
  • 70
  • 139
  • 1
    **Commenters:** comments are for getting clarification and to provide feedback so the answer can be improved, not for extended discussion. If you like this answer, vote it up. If you don't like this answer, vote it down and/or provide your own answer. If you'd like to discuss the topic of this question with others, please [use chat](http://chat.stackexchange.com/rooms/21). –  Aug 01 '11 at 11:03
  • 9
    @DeadMG: It's **100% incorrect and missing the point** to say *This is definitely a downgrade from C++, which just has `std::function` and you're done.* Why? Because you also, for example, have function pointers. It's **exactly** the same thing in D: D's "functions" are function pointers, and D's "delegates" are the same as C++'s `std::function` (except that they're built-in). There's no "downgrade" anywhere whatsoever -- and there's a 1:1 correspondence between them, so it shouldn't at all be confusing if you're familiar with C++. – user541686 Aug 01 '11 at 12:43
  • 10
    @Mark Trapp: I must admit that I don't quite understand your stance on the topic – comments are not to be used for, you know, _commenting_ on an answer? – dnadlinger Aug 01 '11 at 13:26
  • @klickverbot Commenting exists only as a means to improve questions & answers (the main focus of the site) and are obsolete once the answer has been improved based on feedback; I checked with DeadMG before cleaning them up. Any extended discussion should take place in chat, not here. See [our FAQ](http://programmers.stackexchange.com/faq) and [Should moderators delete obsolete comments?](http://meta.stackexchange.com/q/77373/149432) for more information and feel free to ask a question on [our meta-discussion site](http://meta.programmers.stackexchange.com) if you have any additional concerns. –  Aug 01 '11 at 13:50
  • 6
    @Mark Trapp: My point is that most of the comments here were _not_ obsolete (the meta discussion you linked specifically applies to suggestions which have already been incorporated into the original post), but pointed out factual inaccuracies in the post, which are still present. – dnadlinger Aug 01 '11 at 19:26
  • @klickverbot If an answerer has not opted to improve his answer to incorporate all feedback, the appropriate way to handle it is to down-vote the answer and/or provide a better one. Comments are second-class citizens and are transient by design. –  Aug 01 '11 at 19:37
  • 2
    @Mark Trapp: Ah, okay – this should probably be stated clearly somewhere (e.g. the FAQ) though. – dnadlinger Aug 01 '11 at 19:53
  • @klickverbot that's a fair point: I've created [a question](http://meta.programmers.stackexchange.com/questions/2044/how-can-we-improve-our-guidance-about-the-purpose-of-comments) on our meta-discussions site to discuss ways we can improve our comment guidance. –  Aug 01 '11 at 20:34
  • 7
    A note on format: D's format function is typesafe (solves the safety/overflow issues) and does not violate DRY as the format string only specifies how the arguments should be formatted, not their types. This is possible thanks to D's typesafe variadics. Therefore, that objection at least is completely invalid. – Justin W Aug 01 '11 at 22:05
  • @Mehrdad: That's what *I* said when my friend asked me for help with functions and delegates. Apparently, it didn't quite work out that way. Maybe it was just a compiler bug? – DeadMG Aug 03 '11 at 21:57
  • @DeadMG: I'm pretty sure that's correct, but I obviously can't tell without any code... do you happen to remember an example of the code? (Also, I agree with Justin's analysis -- there's **nothing wrong** with D's string formatting, as compared to C.) – user541686 Aug 03 '11 at 22:23
  • 17
    @Mark: Whatever is the current policy regarding them, ___I find it stupid and hindering that comment discussions are deleted___. I think this answer had extensive discussions (which I'm now interested in), but I'm not sure, and I have no way to find out. That room you linked to has well over 10k messages, and I have no chance at all to find a discussion I seem to remember having taken place, but can't remember the content of. ___Discussions regarding this answer belong here, to this answer___, and not to some chat room, where they might be mingled in discussions of sex, drugs, and rock'n'roll. – sbi Aug 25 '11 at 13:00
1

The most important thing that C++ "does better" than D is interfacing with legacy libraries. Various 3D engines, OpenCL and alike. As D is new, it has far smaller amount of different libraries to choose from.

Another important difference between the C++ and the D is that the C++ has multiple financially independent vendors, but as of 2014 the D has practically only one, the 2-man team that created it. It's interesting that the "second source principle" that says that projects should never depend on technology, components, that have only one, single, vendor, seems to hold even for software.

For comparison, the first version of the Ruby interpreter was written by the inventor of Ruby, the Yukihiro Matsumoto, but the 2014 era mainstream Ruby interpreter has been written practically from scratch by other people. Therefore, Ruby can be seen as a language that has more than one financially independent vendor. D, on the other hand, can be an awesome technology, but it depends on the few developers that develop it.

The history of Java shows that even if a technology, in this case, Java, has a fine, but single, financier, there is a great risk that the technology is essentially dumped, regardless of the huge corporate user base. A citation of the Apache Software Foundation, where the EC stands for "Executive Committee":

Oracle provided the EC with a Java SE 7 specification request and license that are self-contradictory, severely restrict distribution of independent implementations of the spec, and most importantly, prohibit the distribution of independent open source implementations of the spec.

As a historical note, it can be said that Java applets had hardware accelerated 3D canvas years before the HTML5 WebGL got developed. The Java applets start-up speed issue could have been solved, if the Java had been a community project, but the executives of the sole financier of the Java, the Sun Microsystems, did not find it important enough to have the Java implementation fixed. The end result: HTML5 canvas by multiple vendors as a "poor man's replica" of the Java GUI frameworks (Swing, etc.). Interestingly enough, on the server side the Python programming language has the same advantages that the Java promised: write once, run on every server, regardless of hardware, provided that the Python application is not compiled to machine code. The Python is about as old/young as the Java is, but unlike the Java, it's backed up by more than one independently financed team of developers (the PyPy and the main stream Python interpreter).

Summary:

When evaluating technology for production use, the most important properties of technologies are the people, who develop it, the social process, where the development takes place, and the number of financially independent development teams.

Whether it's more advantageous to use technology T1 over technology T2 depends on the vendors of the technologies and whether technology T1 allows to solve project related problems more cheaply than T2. For example, if the single supplier issue were ignored, then for information systems the Java would be a "better" technology than the C++, because Java binaries do not need recompilation at deployment to new hardware and the amount of memory management related software development work is smaller for the Java than it is for the C++. Projects that are developed from scratch, e.g. projects that do not depend on other libraries, might be cheaper to develop in D than C++, but, on the other hand, the C++ has more than one vendor and therefore is less risky in the long run. (The Java example, where the Sun Microsystems was almost OK, but the Oracle practically made Java the "new COBOL".)

A Possible Workaround to some of the C++ Limitations

One of the possible workarounds to some of the limitations of the C++ is to use a design pattern, where the initial task is taken to pieces and the pieces are "sorted" (classified, clustered, divided, other-nice-words-for-the-same-thing) to 2 classes: control logic related tasks, where memory access patterns do not allow locality; signal processing-like tasks, where locality is easily achieved. The signal processing "like" tasks can be implemented as a set of relatively simplistic console programs. The control logic related tasks are placed all to a single script that is written in Ruby or Python or otherwise something, where software development comfort has higher priority than speed.

To avoid expensive operating system process initialization and data copying between operating system processes, the set of small C++ console applications might be implemented as a single C++ program that is booted as a "servlet" by the Ruby/Python/etc. script. The Ruby/Python/etc. script shuts the servlet down before exit. Communication between the "servlet" and the Ruby/Python/etc. script takes place over a Linux named pipe or some similar mechanism.

If the initial task does not lend itself to be easily divided to pieces that can be classified to the 2 aforementioned classes, then a thing to try might be to re-phrase the problem so that the initial task changes.

1

One thing I appreciate in C++ is the ability to document a function argument or return value as a C++ reference instead of a pointer, hence implying taking a non-null value.

D version:

class A { int i; }

int foo(A a) {
    return a.i; // Will crash if a is null
}

int main() {
    A bar = null;
    // Do something, forgetting to set bar in all
    // branches until finally ending up at:
    return foo(bar);
}

C++ version:

class A { int i; };

int foo(A& a) {
    return a.i; // Will probably not crash since
                // C++ references are less likely
                // to be null.
}

int main() {
    A* bar = null;
    // Do something, forgetting to set bar in all
    // branches until finally ending up at:
    // Hm.. I have to dereference the bar-pointer
    // here, otherwise it wont compile.  Lets add
    // a check for null before.
    if (bar)
        return foo(*bar);
    return 0;
}

To be fair you can get very close to C++ by making A into a D struct and marking the foo()-argument as a ref (classes are reference types and structs are value types in D, similar to C#).

I believe there is a plan to create a NonNullable template for classes as a D standard library construct instead. Even so I like the brevity of just Type& compared to NonNullable(Type), and would prefer non-nullable as the default (rendering something like Type and Nullable(Type)). But it's too late to change that for D and I'm drifting off-topic now.

lumor
  • 21
  • 2
  • 3
    Both function arguments and return values in D can be marked with `ref` to give you the same effect as C++'s `&`. The one major difference is that `ref` won't take a temporary even if it's `const`. – Jonathan M Davis Aug 01 '11 at 01:29
  • I like the way returning references to local variables is prohibited in D, I was not aware of that until I read your comment. But you can still return a non-local null reference without thinking about it in a way where the C dereference operator will make you think. – lumor Aug 02 '11 at 20:57
  • You're confusing things. Classes are _always_ references, and that's separate from ref. References in D are like references in Java. They're managed pointers. Passing or returning by ref is like passing or returning with & in C++. Passing a class reference by ref is like passing a pointer in C++ with & (e.g. A*&). Classes don't go on the stack. Yes, NonNullable would make it possible to have a class reference which was guaranteed to be non-null, but that's completely separate from ref. What you're trying to do in the C++ code doesn't work in D because classes don't go on the stack. Structs do. – Jonathan M Davis Aug 02 '11 at 22:51
  • 1
    So yes, being able to have a class reference which is non-nullabe would be nice, but C++ manages to do what you're showing because it allows for classes to be on the stack and allows for pointers to be dereferenced. And while you _can_ dereference pointers in D, classes are references, not pointers, so you can't dereference them. Since you can't put them on the stack, and you can't dereference them, there is no way built into D to have a class which can't be null. It _is_ a loss, but NonNullable will fix it, and the gains from the separation of structs and classes are generally greater anyway. – Jonathan M Davis Aug 02 '11 at 23:13
  • Okay, I removed `ref` from the example since it doesn't improve the situation for classes. I had it in there because you mentioned it in your first comment. I also added a note about structs. Personally I am fully aware of D classes being reference types and not being placed on the stack without using something like `emplace`. Sorry for being unclear, my first version of the answer did probably deserve a -1. Please tell me if I can do something more to clarify the situation. – lumor Aug 03 '11 at 21:29
  • 2
    C++ references cannot be null by the language standard (saying "probably will not be null" is incorrect since it can't be null). I do wish there was a way to disallow null as a valid value for a class. – jsternberg Aug 03 '11 at 22:01
  • In my experience C++ references can be null in optimized builds since no exception is thrown when dereferencing a null pointer and passing it on to the reference in those kind of builds. – lumor Aug 04 '11 at 17:45
  • Well, aside from whether C++ references can ever be null, the current answer looks correct. – Jonathan M Davis Aug 04 '11 at 19:52