1

When I declare a handle to a nested value type, Visual Studio (2008 and 2010) gives me the following compiler error:

error C2248: 'Outer::Inner' : cannot access private struct declared in class 'Outer'

Here's the code that fails to compile:

ref class Outer
{
private:
    value struct Inner { };

void F()
{
    Inner i; // OK
    Inner^ h; // C2248
}
};

The problem goes away when I change the type of Inner from value struct to ref struct.

ref class Outer
{
private:
    ref struct Inner { };

void F()
{
    Inner i; // OK
    Inner^ h; // OK
}
};

Is there a rule about handles to nested value types that I have missed, or is this a VS bug?

Sebacote
  • 690
  • 6
  • 17

1 Answers1

3
  Inner^ h; // C2248

This is a very common mistake in C++/CLI, a mistake that you can never make in other .NET languages like C# and VB.NET. Languages that automatically figure out whether a type is a value type or a reference type from the declaration. The hat was added to the C++/CLI syntax to make it resemble C++.

Creating a reference to a value type value requires the value to be stored on the garbage collected heap. This is called a "boxing conversion", the value is embedded in an object. Otherwise the magic that creates the illusion that a struct type inherits from ValueType which inherits from Object. Boxing conversions are needed in a few cases, such as calling the virtual methods inherited from System::Object (Equals, GetHashCode, ToString). Which the compiler takes care of without you helping, automatically emitting the boxing conversion code.

Intentionally boxing the value, like your statement does, has very few practical uses. I cannot think of any, but that's perhaps due to my lack of imagination. The boxing and unboxing code you will invoke, unseen, when accessing the variable just adds a lot of overhead that slows down your code. Value types were invented to speed-up code, you'll want to avoid boxing where you can.

Explaining the compiler error away is not so easy. One could argue that the compiler doesn't permit accessing the type internals that makes boxing possible because it is private. But hard to make that case, note that calling i.ToString() produces the same error. While it is fine when it is a private reference type. Accessibility should never depend on the kind of type, this kind of inconsistency is usually called "bug" ;) You can post to connect.microsoft.com to make your case.

The best workaround is the sane one: just don't use the hat. Declare a reference type when you want reference semantics.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you for your answer. What I actually wanted to do was to override the "Equals(Object obj)" method, where I have to check whether "obj" is an instance of the Inner type, like this: Inner^ i = dynamic_cast(obj); This is the equivalent of the C# "is" keyword. Unfortunately, this won't compile; in fact, VS absolutely refuses to box an instance of a nested value type. The dynamic_cast works fine for non-nested value types. – Sebacote Apr 22 '13 at 18:45
  • Hard to make sense of this, a value struct type cannot be derived from so using dynamic_cast<> is not indicated. And your type is private so no code other than your own could ever box this struct. Ask another question, I would need to see the context of this usage to suggest a better alternative. – Hans Passant Apr 22 '13 at 19:14
  • Simple scenario: you have a private dictionary whose key is a private, nested value type with two string members (a and b). Two keys are equals if a and b are equals in a case-insensitive manner. The "outer" class user passes a and b to some method to insert data. You want to check whether the key exists before inserting, so you override Equals on the private value type to make the case-insensitive comparison. But anyway, that is not really the point. The point is that the C++/CLI compiler behaves strangely with nested value types. As you have noticed, none of the Object methods can be called. – Sebacote Apr 22 '13 at 19:53
  • Right, not nice. Well, you can file a bug at connect about it. Meanwhile, you *really* don't want a struct as a key for a Dictionary, the default implementation of GetHashCode for a value struct has very unpleasant quirks. Be sure to make it a ref struct. – Hans Passant Apr 22 '13 at 23:33
  • Interesting. Can you elaborate or point me to some resource about those pitfalls? – Sebacote Apr 23 '13 at 00:41