[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?
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.
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]
- 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++.