C2440: Generic argument and nullptr

T

Tony Maresca

I'm having a hard time understanding why I get
the following error with the code shown.

Can someone explain?

public ref class Class1
{
};

generic<typename T> where T: Class1, gcnew()
public ref class Gen
{
public:
Gen() {}

T GetVal(bool condition)
{
if( condition )
return gcnew T();
else
return nullptr; // <----- Error: C2440
}

};

public ref class test
{
public:
test()
{
Gen<Class1^> gen = gcnew Gen<Class1^>();
Class1^ x1 = nullptr;
Class1^ x2 = gcnew Class1();
Class1^ x3 = gen->GetVal(false);
}
};

// C2440: 'return' : cannot convert from 'nullptr' to 'T'


--
Tony M.
 
C

Carl Daniel [VC++ MVP]

Tony Maresca said:
I'm having a hard time understanding why I get
the following error with the code shown.

Can someone explain?

Read the entire error message:

genericnull1016.cpp(16) : error C2440: 'return' : cannot convert from
'nullptr' to 'T'
'T' may be a value type at runtime: consider using 'T()' instead

Since there's no provision in .NET generics to restrict a type parameter to
be a reference type (or a value type, for that matter), there's no way to
represent nullptr within a generic.

In short, you'll have to change your design - you simply can't count on
being able to return nullptr from a generic method.

-cd
 
C

Carl Daniel [VC++ MVP]

Tony said:
Excuse me, but I showed the entire error message
in my OP:

"C2440: 'return' : cannot convert from 'nullptr' to 'T'"

That was the entire error message, nothing more.

Check the output window instead of the task list. Many C++ compiler error
messages are more than 1 line long - the task list/error lists shows only
the first line of the message.
Sorry, but it looks to me that the constraint explicitly
requires a reference type for the generic parameter.

I agree - it looks like it should require a ref class, since a value type
can't derive from a ref class. Apparently the C++ compiler disagrees - Have
you tried the equivalent code in C#? If that compiles, then this would have
to be a compiler bug, but it may well be a .NET limitation.

-cd
 
T

Tamas Demjen

Tony said:
But, in C# if the type is a reference type, then null can
be assigned to a variable of the generic type.

Have you tried what the error message recommended? This works for me:

T GetVal(bool condition)
{
if( condition )
return gcnew T();
else
return T(); // nullptr
}

Note that in C++ the T() syntax is essencially a default initialization.
If there's no user-defined default constructor, the compiler will simply
fill the variable with 0 bytes. In your case, T is Class1^, so T() is
Class1^(), which is identical to nullptr.

int main()
{
Gen<Class1^> gen;
Class1^ x3 = gen.GetVal(false);
if(x3 == nullptr)
{
Console::WriteLine("success");
}
}

Tom
 
B

Ben Voigt

generic said:
public ref class Gen
{
public:
Gen() {}
- T GetVal(bool condition)
+ T^ GetVal(bool condition)
{
if( condition )
return gcnew T();
else
return nullptr; // <----- Error: C2440
}

};

A return type of 'T' is marked modopt(IsImplicitlyDereferenced), which makes
no sense for nullptr.
 
B

Ben Voigt

Tamas Demjen said:
Have you tried what the error message recommended? This works for me:

T GetVal(bool condition)
{
if( condition )
return gcnew T();
else
return T(); // nullptr
}

Note that in C++ the T() syntax is essencially a default initialization.
If there's no user-defined default constructor, the compiler will simply
fill the variable with 0 bytes. In your case, T is Class1^, so T() is
Class1^(), which is identical to nullptr.

No, T() will return the same as gcnew T(), which is to say a pass-as-copy
tracking reference(!) to a default Class1. The problem is that T is Class1,
not Class1^, and while nullptr is a valid value for Class1^ it isn't a valid
value for Class1. Return a T^ tracking handle instead of a T.
 
T

Tamas Demjen

Ben said:
No, T() will return the same as gcnew T(), which is to say a pass-as-copy
tracking reference(!) to a default Class1. The problem is that T is Class1,
not Class1^, and while nullptr is a valid value for Class1^ it isn't a valid
value for Class1. Return a T^ tracking handle instead of a T.

Sorry, but you're wrong here. The generic is instantiated like this:
Gen<Class1^>

so T is Class1^, not Class1. It really works, return T() returns
nullptr, I verified it with the following test code. T() does not create
any Class1 instance, it creates a nullptr of type Class1^.

public ref class Class1
{
public:
Class1() { Console::WriteLine("c'tor"); }
};

generic<typename T> where T: Class1, gcnew()
public ref class Gen
{
public:
Gen() {}

T GetVal(bool condition)
{
if( condition )
return gcnew T();
else
return T(); // <----- nullptr
}

};

public ref class Test
{
public:
Test()
{
Gen<Class1^> gen;
Class1^ x3 = gen.GetVal(false); // <------ try it: true or false
if(x3 == nullptr)
{
Console::WriteLine("yes, it's nullptr");
}
}
};

This code prints "yes, it's nullptr", which means the code works as I
described it. It doesn't create any instances of Class1... ever. It
returns a nullptr.

Now if I change the test code to gen.GetVal(true), the program prints
"c'tor". Of course in that case a Class1 instance is constructed, and
the result is not nullptr.

T is Class1^, and return T() is essencially the same as return nullptr,
with no Class1 instance constructed whatsoever. I really tried this, I'm
not just guessing here. It's something I didn't know before either.

Tom
 
T

Tamas Demjen

Ben said:
+ T^ GetVal(bool condition)

Unfortunately this doesn't compile. The error message is:

error C3229: 'T ^' : indirections on a generic type parameter are not
allowed
compiler using 'T' to continue parsing

Tom
 
T

Tamas Demjen

Tony said:
Thanks, this is the part that I was missing. I never saw
any reference to the T() syntax as its used here.

The Error List window only shows the first line of error messages. Your
original error message had two lines, as Carl explained it earlier. To
read the full error message, switch to the Output tab.

I didn't know all this stuff either. I just compiled your code, and
noticed that the compiler suggested to use return T(). First I thought
it was ridiculous as hell, but tried it nonetheless, and it turned out
to be the solution. First it may look like return T() creates an
instance of Class1, but in this case it just creates a nullptr. Yes, it
is very odd, terribly confusing too, I have to admit it.

Tom
 
B

Ben Voigt

Tamas Demjen said:
Sorry, but you're wrong here. The generic is instantiated like this:
Gen<Class1^>

Sorry I missed that. Then T() is indeed just the C++ equivalent of C#'s
default keyword and will be nullptr.

But then shouldn't gcnew T() also give nullptr?

In C++, T() and new T() both call the same constructor, which is to say the
default constructor for T. Outside a generic, gcnew works essentially the
same as new, only it constructs an object on the managed heap instead of
unmanaged, but like new it allocates heap memory and returns an address.
 
B

Ben Voigt

Tamas Demjen said:
Unfortunately this doesn't compile. The error message is:

error C3229: 'T ^' : indirections on a generic type parameter are not
allowed
compiler using 'T' to continue parsing

Tom

I never would have guessed that.... but then I use templates in C++ and
generics in C#. Template semantics are well known and understood, why
couldn't Microsoft just build generics on top of what we already know?
 
T

Tamas Demjen

Ben said:
But then shouldn't gcnew T() also give nullptr?

In C++, T() and new T() both call the same constructor, which is to say the
default constructor for T.

I agree, I have a feeling that generics are implemented in a very
confusing way in C++/CLI. Just consider the where clause:

where T: Class1

it's a constraint that specifies that T can only be Class1^. The whole
generics implementation looks C#-ish to me, but that's inexact in C++.
In some of the expressions T stands for Class1, and in others it
represents Class1^. It's a big mess, if you ask me. (It's fine for C#,
but is so not C++-like).

A possible reason behind this is that T can be either a value type or a
reference type, and they had to find a way to use a uniform syntax for
both. That's only possible if T is interpreted in a context-sensitive
way (sometimes as Class1, sometimes as Class1^).

I discovered the following context rule. If T is a value type, T()
creates a new object, and gcnew T() generates an error. If T is a
reference type, T() is nullptr and gcnew T() creates a new object.

Tom
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top