views:

58

answers:

1

I want to create a generic IEnumerable implementation, to make it easier to wrap some native C++ classes. When I try to create the implementation using a template parameter as the parameter to IEnumerable, I get an error.

Here's a simple version of what I came up with that demonstrates my problem:

ref class A {};

template<class B>
ref class Test : public System::Collections::Generic::IEnumerable<B^> // error C3225...
{};

void test()
{
    Test<A> ^a = gcnew Test<A>();
}

On the indicated line, I get this error:

error C3225: generic type argument for 'T' cannot be 'B ^', it must be a value type or a handle to a reference type

If I use a different parent class, I don't see the problem:

template<class P>
ref class Parent {};

ref class A {};

template<class B>
ref class Test : public Parent<B^> // no problem here
{};

void test()
{
    Test<A> ^a = gcnew Test<A>();
}

I can work around it by adding another template parameter to the implementation type:

ref class A {};

template<class B, class Enumerable>
ref class Test : public Enumerable
{};

void test()
{
    using namespace System::Collections::Generic;
    Test<A, IEnumerable<A^>> ^a = gcnew Test<A, IEnumerable<A^>>();
}

But this seems messy to me. Also, I'd just like to understand what's going on here - why doesn't the first way work?

+2  A: 

In your first example, your inheritance line should read:

ref class Test : public System::Collections::Generic::IEnumerable<B>

(no reference marker on the template)

Then your usage line should read:

Test<A^> ^a = gcnew Test<A^>();

The reference markers go in the instantiation of the template, not the template itself.

Here's your sample, compilable:

using namespace System;
using namespace System::Collections::Generic;

ref class A {};

template<class B> ref class Test : public System::Collections::Generic::IEnumerable<B>
{
public:
    B GetInstance()
    {
        return Activator::CreateInstance<B>();
    }

    virtual System::Collections::IEnumerator^ GetEnumeratorObj() =
        System::Collections::IEnumerable::GetEnumerator
    {
        return nullptr;
    }

    virtual System::Collections::Generic::IEnumerator<B>^ GetEnumerator()
    {
        return nullptr;
    }
};

void test()
{
    Test<A^> ^a = gcnew Test<A^>();
}

Edit: Realized I should explain why this is. To the best of my understanding, the reason that you can't specify B^ in the IEnumerable inheritance is that IEnumerable is a generic with a constraint on it, while B is a template parameter which is unconstrained. Templates allow for much more flexible syntax, even when they govern ref objects, as they're still effectively "parsed text" even in C++/CLI. However, when they bump into generics with constraints, the rules get a lot tighter.

Dan Story
Thanks, that solves the initial problem. I left part of the question out, though - I would also like to create new instances of B inside of the the Test class. With your solutions, gcnew B() fails becuase B is a handle type, is there a way around that?
fiirhok
Yeah, although it's not at all intuitive. Use `Activator::CreateInstance<T>()`. I updated the sample above to demonstrate.
Dan Story