Jump to page: 1 24  
Page
Thread overview
const(Class) is mangled as Class const* const
Mar 26, 2017
Benjamin Thaut
Mar 26, 2017
deadalnix
Mar 26, 2017
Namespace
Mar 26, 2017
Jonathan M Davis
Mar 26, 2017
Jerry
Mar 27, 2017
Jonathan M Davis
Mar 26, 2017
Benjamin Thaut
Mar 26, 2017
deadalnix
Mar 26, 2017
Jerry
Mar 27, 2017
deadalnix
Mar 27, 2017
Walter Bright
Mar 27, 2017
kinke
Mar 27, 2017
Walter Bright
Mar 27, 2017
kinke
Mar 27, 2017
Walter Bright
Mar 27, 2017
kinke
Mar 27, 2017
Walter Bright
Mar 27, 2017
kinke
Mar 28, 2017
Walter Bright
Mar 28, 2017
Jacob Carlborg
Mar 28, 2017
Walter Bright
Mar 28, 2017
Jonathan M Davis
Mar 28, 2017
kinke
Mar 28, 2017
deadalnix
Mar 28, 2017
kinke
Mar 28, 2017
deadalnix
Mar 28, 2017
kinke
Aug 26, 2020
Manu
Aug 26, 2020
Nathan S.
Aug 27, 2020
Nathan S.
Aug 26, 2020
Walter Bright
March 26, 2017
Consider the following C++ and D source:

class Class
{
	virtual ~Class(){}
};

// mangles as ?getClass@@YAPEAVClass@@XZ
Class * getClass() { return nullptr; }

// mangles as ?getClassConst@@YAPEBVClass@@XZ
const Class * getClassConst() { return nullptr; }

// mangles as ?getClassConstConst@@YAQEBVClass@@XZ
const Class * const getClassConstConst() { return nullptr; }

extern(C++)
{
  class Class
  {
    void _cppDtor() {}
  }

  // Mangles as ?getClass@@YAPEAVClass@@XZ
  Class getClass() {return null;}

  // Mangles as ?getClassConst@@YAQEBVClass@@XZ
  const(Class) getClassConst() {return null;}
}

As you see from the above example D mangles the getClassConst as a "Class const * const" instead of a "Class const *" ("YAQEBV" vs "YAPEBV"). Is this expected behavior? The core problem is that D can not express one of the two. Either const(Class) becomes "Class const *" or "Class const * const". I've never seen C++ code that returns const pointers to const classes so I think the default should be "Class const *". Either way its rather bad that we can only represent one or the other. Sooner or later someone will hit this problem again wanting the other option or both. Any idea how we can avoid changing C++ source code in order to bind it to D? Using pragma(mangle,) works but is kind of ugly. Especially because the string to pragma(mangle,) can't be genearted using CTFE.

Should I open a bug for this?
March 26, 2017
On Sunday, 26 March 2017 at 10:43:11 UTC, Benjamin Thaut wrote:
> As you see from the above example D mangles the getClassConst as a "Class const * const" instead of a "Class const *" ("YAQEBV" vs "YAPEBV"). Is this expected behavior?

It's consistent. D's const is transitive, and D doesn't allow you to specify const on the indirection of a reference type. So there is no problem on the C++ mangling side of things, but, arguably, there is one in D's sementic, that isn't new.

Something like differentiating "const(C) i" and "const C i" may be a good idea.
March 26, 2017
On Sunday, 26 March 2017 at 14:30:00 UTC, deadalnix wrote:
> On Sunday, 26 March 2017 at 10:43:11 UTC, Benjamin Thaut wrote:
>> As you see from the above example D mangles the getClassConst as a "Class const * const" instead of a "Class const *" ("YAQEBV" vs "YAPEBV"). Is this expected behavior?
>
> It's consistent. D's const is transitive, and D doesn't allow you to specify const on the indirection of a reference type. So there is no problem on the C++ mangling side of things, but, arguably, there is one in D's sementic, that isn't new.
>
> Something like differentiating "const(C) i" and "const C i" may be a good idea.

After reading your post, I wonder: How could I translate the following C++ code to D?

----
int a = 2;
int* const p = &a;
----
March 26, 2017
On Sunday, March 26, 2017 14:51:28 Namespace via Digitalmars-d wrote:
> On Sunday, 26 March 2017 at 14:30:00 UTC, deadalnix wrote:
> > On Sunday, 26 March 2017 at 10:43:11 UTC, Benjamin Thaut wrote:
> >> As you see from the above example D mangles the getClassConst as a "Class const * const" instead of a "Class const *" ("YAQEBV" vs "YAPEBV"). Is this expected behavior?
> >
> > It's consistent. D's const is transitive, and D doesn't allow you to specify const on the indirection of a reference type. So there is no problem on the C++ mangling side of things, but, arguably, there is one in D's sementic, that isn't new.
> >
> > Something like differentiating "const(C) i" and "const C i" may
> > be a good idea.
>
> After reading your post, I wonder: How could I translate the following C++ code to D?
>
> ----
> int a = 2;
> int* const p = &a;
> ----

You don't. Once part of a type is const, everything inside it is const. So, if a pointer is const, everything it points to is const. You can have the outer part be mutable with the inner part be const, but not the other way around. D's const can do tail-const, but it can't do head-const.

Now, while you can't use const to make the pointer const and what it points to mutable, you _can_ make the the pointer read-only while still having what it points to be fully mutable by putting it in a struct which restricts write-access to the pointer. I believe that that's essentially what std.experimental.typecons.Final is supposed to do.

Personally, I don't think that the fact that you can't use const for head-const in D is really a loss, since it's almost never what you want. Tail-const is _way_ more useful. But it is true that by making D's const fully transitive, there are variations of constness that C++ can do that D can't. immutable pretty much forces that though, and it does simplify the language.

- Jonathan M Davis

March 26, 2017
On Sunday, 26 March 2017 at 14:30:00 UTC, deadalnix wrote:
>
> It's consistent. D's const is transitive, and D doesn't allow you to specify const on the indirection of a reference type. So there is no problem on the C++ mangling side of things, but, arguably, there is one in D's sementic, that isn't new.

I disagree. When binding C++ code to D I don't care about D's const rules. I care about the C++ const rules. There are thousands of C++ libraries out there that can't be bound to D because they use const Class* instead of const Class* const. So in my eyes there is definitly something wrong with the C++ mangling of D.


March 26, 2017
On Sunday, 26 March 2017 at 15:29:02 UTC, Jonathan M Davis wrote:
> Personally, I don't think that the fact that you can't use const for head-const in D is really a loss, since it's almost never what you want. Tail-const is _way_ more useful. But it is true that by making D's const fully transitive, there are variations of constness that C++ can do that D can't. immutable pretty much forces that though, and it does simplify the language.


There are quite a few things wrong with const, it's so bad phobos isn't even const-correct when it should be. In cmp() for example, if you pass a lambda that takes the parameters by reference. You will be modifying temporary values that cmp() created on the stack. These should be const, but then what if it is a pointer? It is a different situation and you don't really want const cause you are going to be modifying the correct struct (the one pointed to). It is just easier to not use const in D cause it makes your code more difficult to actually use. That's exactly what Phobos does, it ignores const for the most part because it is easier not to use it.

March 26, 2017
On Sunday, 26 March 2017 at 17:41:57 UTC, Benjamin Thaut wrote:
> On Sunday, 26 March 2017 at 14:30:00 UTC, deadalnix wrote:
>>
>> It's consistent. D's const is transitive, and D doesn't allow you to specify const on the indirection of a reference type. So there is no problem on the C++ mangling side of things, but, arguably, there is one in D's sementic, that isn't new.
>
> I disagree. When binding C++ code to D I don't care about D's const rules. I care about the C++ const rules. There are thousands of C++ libraries out there that can't be bound to D because they use const Class* instead of const Class* const. So in my eyes there is definitly something wrong with the C++ mangling of D.

It is clear that you won't be able to express 100% of C++ in D, that would require to important all the weird parts of C++ into D, but if we are doing so, why use D in the first place ?

Note that using const Class* in C++ is essentially useless. The class remains mutable and the reference is local the the callee anyway, so it doesn't change anything for the caller. Such a pattern is most likely indicative of a bug on the C++ side, or at least of code that do not do what the author intend to.
March 26, 2017
On Sunday, 26 March 2017 at 22:29:56 UTC, deadalnix wrote:
> It is clear that you won't be able to express 100% of C++ in D, that would require to important all the weird parts of C++ into D, but if we are doing so, why use D in the first place ?
>
> Note that using const Class* in C++ is essentially useless. The class remains mutable and the reference is local the the callee anyway, so it doesn't change anything for the caller. Such a pattern is most likely indicative of a bug on the C++ side, or at least of code that do not do what the author intend to.


For `const Class*` the Class is not mutable. It is the case of `Class* const` that Class is mutable. I see a lot of people in D say similar things about it. Saying it is a bug, saying it's a good thing that a const pointer with mutable type isn't in D. Yet they always tend to be the people that have never actually used C++. As is indicative of not even knowing the correct syntax to use in C++. A common matter in C++ is to use templates, you may have seen it, it's a really common pattern, `const T&`. The magic that makes this work is that you read it like this: `T const &`. They both mean the samething but I find makes more sense, conceptually to read it like that. Now substitute a type for T, `int* const &`. You see here that int is mutable for this template. You can see my other post about cmp(), and how Phobos avoids usages of const as it just makes writing generic code difficult.
March 26, 2017
On Sunday, March 26, 2017 18:31:52 Jerry via Digitalmars-d wrote:
> On Sunday, 26 March 2017 at 15:29:02 UTC, Jonathan M Davis wrote:
> > Personally, I don't think that the fact that you can't use const for head-const in D is really a loss, since it's almost never what you want. Tail-const is _way_ more useful. But it is true that by making D's const fully transitive, there are variations of constness that C++ can do that D can't. immutable pretty much forces that though, and it does simplify the language.
>
> There are quite a few things wrong with const, it's so bad phobos isn't even const-correct when it should be. In cmp() for example, if you pass a lambda that takes the parameters by reference. You will be modifying temporary values that cmp() created on the stack. These should be const, but then what if it is a pointer? It is a different situation and you don't really want const cause you are going to be modifying the correct struct (the one pointed to). It is just easier to not use const in D cause it makes your code more difficult to actually use. That's exactly what Phobos does, it ignores const for the most part because it is easier not to use it.

There are significant pros and cons when comparing C++ and D's const, but they usually relate to the ability to get around const in C++ and the lack of ability to do so in D. The ability to create a const pointer to a mutable element is occasionally something that someone wants to do, but in my experience, it's pretty useless. I'm not sure that I've ever used it in C++ (and I've programmed professinally primarily in C++), and when I was learning Java and found out that that's what Java's final did, I decided that all it was good for was constants of built-in types, and I think that that's mostly what I've seen other folks do with it. Having a pointer/reference that can't change while what it points to can just isn't very useful. It's having a mutable pointer to const that's useful.

Now, as for const in D in general and how it compares to C++, that's a rather difficult question and highly debatable (and it's been debated here on many occasions). C++'s const is little more than documentation. It prevents accidental mutation, but there are so many ways around it that it's borderline meaningless. It only works as well as it does, because it's a convention that programmers _usually_ hold to. But it gurantees almost nothing. For instance, it's perfectly possible to have a class where all of its members are const but where all of them mutate the state of the object. The fact that that doesn't normally happen is simply because fortunately, programmers normally choose to behave themselves with mutable and casting away const. But they don't have to, and the compiler doesn't enforce it.

D's const, on the other hand, has strong guarantees about something that's const never being mutated unless something elsewhere in the code has access to a mutable reference and changes the data that way. The const reference is never able to change the data without violating the type system. So, D's const actually means something. It provides real guarantees. But the reality of the matter is that that's so restrictive that pretty quickly you simply can't use const. There are too many idioms that require backdoors to const - mutexes, memory management, lazy initializaiton, etc.

So, the end result is that in C++, you don't have much in the way of guarantees, but you're able to use what you do have all over the place. Accidental mutation is prevented and programmer intention is conveyed, but not much of anything is really guaranteed. Whereas with D, you have the strong guarantees, and you can use const in certain circumstances, but you're quickly forced to use it in only very restricted circumstances - especially if user-defined types or templates are involved. Too much simply doesn't work with true const. What most code really needs is logical const, and the compiler can't guarantee that.

As to whether C++ or D did a better job with const, I really don't know. I find C++'s const to be pretty terrible from the standpoint of what it really protects, and I only recall one time in my entire programming career that it's actually caught a bug for me, but it's still nice to be able to indicate that a function doesn't mutate its arguments, even if it's not actually guaranteed, since it's usually true. The fact that D's const provides the stronger guarantees is fantastic, but it's also so restrictive that it borders on useless. So, ultimately, I'm not very happy with either solution, and I don't know what the best one would be.

Arguably, immutable is what's truly useful - though obviously, code has to be written with it in mind, because a lot of code won't work with it without that - but for it to do its job, D's const pretty much has to be the way it is. Even if we wanted C++'s const in D, we couldn't have it unless it didn't interoperate with immutable.

And as for the C++ concept of "const-correctness", I think that the end result of all of this is that it really doesn't apply to D. When code is const-correct, that essentially means that it will compile with const and that it's theoretically, "logically" const - _not_ that it's actually const. It's about convention and conveying the intention of the programmer, not actually guaranteeing anything. And D's const is all about guarantees. For better or worse, I think anyone thinking about const-correctness with D code is almost certainly thinking about const the wrong way in D.

And yes, the end result of all of this is that templated code can almost never use const. So, a _lot_ of Phobos doesn't use it. And on some level, that sucks, but I'm not sure that we'd really be any better off with C++'s const either. There are pros and cons both ways.

- Jonathan M Davis

March 27, 2017
On Sunday, 26 March 2017 at 17:41:57 UTC, Benjamin Thaut wrote:
> There are thousands of C++ libraries out there that can't be bound to D because they use const Class* instead of const Class* const. So in my eyes there is definitly something wrong with the C++ mangling of D.

I agree that C++-mangling a const D object reference as `const T *const` isn't helpful although it would be consistent with D semantics. As deadalnix pointed out, the const for the pointer itself only concerns the callee and not the caller. I sometimes use `void foo(const T *bla); ... void foo(const T *const bla) { ... }` if I find it useful to make clear that `bla` won't change in my foo() implementation, but I never use the second const in the function declaration in the header as it's just useless clutter for the caller.

Having said that, you can only declare a C++ type as D class if it's exclusively passed and returned as pointer (at least in the parts you are going to interface with via D). This was true for the C++-based DMD front-end and would also be true for some types used in LLVM. But as soon as you want to interface with a C++ function taking an object as `[const] T&`, afaik you're f*cked and need to declare it as D struct. So I'm quite skeptical that I'll often be able to use D classes to represent C++ types.
« First   ‹ Prev
1 2 3 4