views:

742

answers:

2

[All of the following was tested using Visual Studio 2008 SP1]

In C++, const qualification of parameter types does not affect the type of a function (8.3.5/3: "Any cv-qualifier modifying a parameter type is deleted")

So, for example, in the following class hierarchy, Derived::Foo overrides Base::Foo:

struct Base
{
    virtual void Foo(const int i) { }
};

struct Derived : Base
{
    virtual void Foo(int i) { }
};

Consider a similar hierarchy in C++/CLI:

ref class Base abstract
{
public:
    virtual void Foo(const int) = 0;
};

ref class Derived : public Base
{
public:
    virtual void Foo(int i) override { }
};

If I then create an instance of Derived:

int main(array<System::String ^> ^args)
{
    Derived^ d = gcnew Derived;
}

it compiles without errors or warnings. When I run it, it throws the following exception and then terminates:

An unhandled exception of type 'System.TypeLoadException' occurred in ClrVirtualTest.exe

Additional information: Method 'Foo' in type 'Derived'...does not have an implementation.

That exception seems to indicate that the const qualification of the parameter does affect the type of the function in C++/CLI (or, at least it affects overriding in some way). However, if I comment out the line containing the definition of Derived::Foo, the compiler reports the following error (on the line in main where the instance of Derived is instantiated):

error C2259: 'Derived': cannot instantiate abstract class

If I add the const qualifier to the parameter of Derived::Foo or remove the const qualifier from the parameter of Base::Foo, it compiles and runs with no errors.

I would think that if the const qualification of the parameter affects the type of the function, I should get this error if the const qualification of the parameter in the derived class virtual function does not match the const qualification of the parameter in the base class virtual function.

If I change the type of Derived::Foo's parameter from an int to a double, I get the following warning (in addition to the aforementioned error, C2259):

warning C4490: 'override': incorrect use of override specifier; 'Derived::Foo' does not match a base ref class method

So, my question is, effectively, does the const qualification of function parameters affect the type of the function in C++/CLI? If so, why does this compile and why are there no errors or warnings? If not, why is an exception thrown?

+6  A: 

Well, it's a bug. The const modifiers is emitted into the metadata with the modopt custom modifier. Unfortunately, the C++/CLI language rules do not match the CLI rules. Chapter 7.1.1 of the CLI spec says:

Custom modifiers, defined using modreq (“required modifier”) and modopt (“optional modifier”), are similar to custom attributes (§21) except that modifiers are part of a signature rather than being attached to adeclaration. Each modifer associates a type reference with an item in the signature.

The CLI itself shall treat required and optional modifiers in the same manner. Two signatures that differ only by the addition of a custom modifier (required or optional) shall not be considered to match. Custom modifiers have no other effect on the operation of the VES.

So, the CLR says that Derived::Foo() is not a override, C++/CLI says it is. The CLR wins.

You could report the bug at connect.microsoft.com but it probably a waste of time. I think this incompatibility was intentional. They should have changed the language rules for C++/CLI but surely thought C++ compatibility to be more important. CV modifiers are a pain anyway, there are other scenarios that are not well supported, const pointers to const for one. This cannot be enforced at runtime anyway, the CLR has no support for it.

Hans Passant
Thank you. I had looked in the C++/CLI spec, but didn't think to look in the CLI spec. Not having used .NET much, I didn't know about the CLI's limited support for const qualification until I started digging into this issue. As a follow-up, is there a reason the C++/CLI compiler couldn't just leave out the custom modifier when it generates the IL?
James McNellis
It can't, it loads class definitions from the assembly metadata. Not header files. Those definitions needs to be encoded uses the metadata capabilities. Leaving out CV qualifiers completely would we too incompatible.
Hans Passant
That sounds like you'd get a different behavior if you're just #including the header from the same library vs. calling another DLL... Crazy :(
Nathan Monteleone
I am not so sure. cv-qualification of function parameters affects only the definition of the function; it does not affect the type of the function, and placing cv-qualifiers in the declaration is simply gratuitous. From Herb Sutter's rationale for C++/CLI (§3.5), the modifiers are used "[to imbue] CLI types with extended C++ language-specific semantics." Since the _type_ is not affected by cv-qualification in this case, I don't understand why the cv-qualifier needs to be added to the metadata (I'm sure there has to be a reason, though... I'm just not familiar enough with CLI to know why).
James McNellis
Key point is to realize what metadata is used for. It replaces header files.
Hans Passant
@Ben: keep looking, I bet there's another small irrelevant detail you can downvote.
Hans Passant
Your answer states that C++/CLI can't implement the rule as stated, because the CLR doesn't allow it. This is wrong and IMO hardly irrelevant.
Ben Voigt
@Ben: I guessed at compatibility between C++/CLI and C++ being the issue. I don't know this for a fact, I'm not privy to the MSFT team. If you are, please correct my post. That would be a positive contribution.
Hans Passant
The original poster had the answer to why the CLR isn't to blame at the very top of his question (8.3.5/3: "Any cv-qualifier modifying a parameter type is deleted") and as far as I can tell that was there before you answered. Claiming that the C++ compiler can't remove the attribute, when the standard requires it to remove the qualifier, is disingenuous at best. I didn't realize you wanted me to use my mystical MVP powers to ask the C++/CLI compiler team to comment on this, in fact you discouraged the OP from reporting the bug to microsoft. But I'll point out this thread to them.
Ben Voigt
@Ben : Disingenuous? Mystical? Not quite sorry I never met you at the MVP summits. Do me a favor and take advantage of anonymous voting available at SO. Don't forget to update the post with your findings please.
Hans Passant
When I posted the question, I was well aware of how C++ handles function types and overriding. What I did not know, however, was whether C++/CLI had different rules wrt cv-qualification or not, or whether this was a problem in the compiler or the runtime. I don't use .NET on a regular basis, and thus I admit I don't know much about it. That having been said, I think there are two more correct ways for the compiler to handle this than to cause a runtime error: either the qualifiers should be removed from the metadata or the compiler should disallow the mismatch for CLI types...
James McNellis
I don't believe either change would affect C++ standards conformance, since both affect only the C++/CLI additions to the language. I still don't see a problem with removing the qualifiers, since they don't affect definitions (though, a lot of smart people made the decision to put them in, so I'm sure there is _some_ reason). In any case, I did, in fact, open an issue on Connect for this (https://connect.microsoft.com/VisualStudio/feedback/details/540788), though it's probably lacking in technical precision since I am still trying to wrap my head around the problem.
James McNellis
@James, excellent feedback article.
Hans Passant
@James: yes, but now that C++/CLI is an ISO standard they have to comply with THAT. And sections 8.8.10.1 and 12.3 say the standard C++ rule for cv-qualifiers applies. @nobugz: I tried undoing my vote several comments ago, but it's not allowed and neither is editing your post. If you want to add the spec sections to your post I'll remove them from mine, they definitely aren't very meaningful except in the context of your explanation.
Ben Voigt
@Ben: I think I agree with you on that. I had thought 33.1.5.3 of the C++/CLI standard required the IsConst modifier, but it doesn't; I just misread it. Thank you. (@nobugz: Thank you, too).
James McNellis
+1  A: 

It's a bug, and it's not specific to C++/CLI.

https://connect.microsoft.com/VisualStudio/feedback/details/100917/argument-const-ness-is-part-of-member-function-type-signature

Fact is, the C++ compiler is supposed to strip off top-level const/volatile. Only const/volatile on the pointed-to type of a pointer or reference matters. If the compiler did that correctly, the CLR wouldn't have a say in what's going on.

BTW this is the IL generated by the compiler with /clr:pure

.class private abstract auto ansi beforefieldinit Base
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 1
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: ret 
    }

    .method public hidebysig newslot abstract virtual instance void Foo(int32 modopt([mscorlib]System.Runtime.CompilerServices.IsConst)) cil managed
    {
    }

}

.class private auto ansi beforefieldinit Derived
    extends Base
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 1
        L_0000: ldarg.0 
        L_0001: call instance void Base::.ctor()
        L_0006: ret 
    }

    .method public hidebysig virtual instance void Foo(int32 i) cil managed
    {
        .maxstack 0
        L_0000: ret 
    }

}

This definitely violates the rule James listed concerning deletion of top-level qualifiers.

Further relevant sections of the C++/CLI spec:

8.8.10.1 Function overriding

[snip]

  1. A derived class function explicitly overrides a base class virtual function having the same name, parameter-type-list, and cv-qualification, by using the function modifier override, with the program being ill-formed if no such base class virtual function exists

12.3 Declarator types

The C++ Standard (§8.3.5/3) is augmented, as follows:
The resulting list of transformed parameter types and the presence or absence of the ellipsis is the function’s parameter-type-list.

So I am led to believe that the rule on deletion of cv-qualifiers applies to C++/CLI as well, because the spec specifically calls out section 8.3.5/3 of ISO Standard C++.

Ben Voigt
For native code, that bug has been fixed, at least as of VS2008 SP1 (the compiler even gives a warning, C4373, "previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers"). I can only reproduce the issue mentioned in the original post with CLI types (i.e., `ref class` types).
James McNellis
@James, I concur that my repro code finally doesn't cause problems with VS2008 SP1 (the first fix didn't actually fix it). And I can repro your problem, since you listed a bug on Connect I'll validate it.
Ben Voigt