Search code examples
.netgenericsvisual-c++c++-cligeneric-collections

Managed class types as type of type parameter T (Error C2670)


I want to create a generic function returning a collection of type ObservableCollection<T>^. Callers pass a managed class type for type parameter T. Here is one of my tries of the generic function, after that explanations of other classes used:

generic<class T>
    where T:CAnimal, gcnew()
        ObservableCollection<T>^ GetItems(AnimalType animalType, int count)
    {
        ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
        for (int i = 0; i < count; i++)
        {
            if (animalType == Dog)
            {
                CDog^ dog = gcnew CDog(i, count - i);
                //CAnimal^ dog = gcnew CDog(i, count - i);
                //T dog = gcnew CDog(i, count - i);
                //T^ dog = gcnew CDog(i, count - i);

                animals->Add(dog);
            }
            //else if (animalType == Cat) { ... }
            //...
        }

        return animals;
    }

Because I tried nearly 1632 ways to make that function work I can't tell you why I've implemented it as is :-/

Class CAnimal is the base class for CDog (assume there are more animals, too). AnimalType is an enumeration intended to be used in the function above to determine the correct class type for instantiating and adding to the collection:

ref class CAnimal
{
public:
    CAnimal(){}
    CAnimal(int _age) { age = _age; }
    int age;
};

ref class CDog : CAnimal {
public:
    CDog(){}
    CDog(int _age, int _other)
        :CAnimal(age), other(_other) {}

    int other;
};

enum AnimalType
{
    Dog,
    Cat,
    Fish,
    // ...
};

Furthermore there's a parent class holding ObservableCollection's of dogs, cats, and so on:

ref class CZoo
{
private:
    ObservableCollection<CDog^>^ dogs;

public:
    CZoo()
    {
        dogs = GetItems<CDog^>(AnimalType::Dog, 3);
    }
};

The compiler throws the following error:

error C2670: 'System::Collections::ObjectModel::Collection::Add' : the function template cannot convert parameter 1 from type 'CDog ^'

Can you tell me what I'm doing wrong?

!!! SOLUTION !!!

Based on David Yaws' answer I ended up moving the constructor parameters into a new function called Initialize and removed the enum AnimalTypes. The final code was changed to the following:

ref class CAnimal
{
internal:
    virtual void Initialize(int _age, int _other)
    {
        age = _age;
        other = _other;
    }

public:
    int age;
    int other;
};

ref class CDog : CAnimal {};
ref class CCat : CAnimal {};

generic<class T>
    where T:CAnimal, gcnew()
        ObservableCollection<T>^ GetItems(int count)
    {
        ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
        for (int i = 0; i < count; i++)
        {
            T animal = gcnew T();
            animal->Initialize(i, count - i);
            animals->Add(animal);
        }

        return animals;
    }

ref class CZoo
{
private:
    ObservableCollection<CDog^>^ dogs;
    ObservableCollection<CCat^>^ cats;

public:
    CZoo()
    {
        dogs = GetItems<CDog^>(3);
        cats = GetItems<CCat^>(7);
    }
};

Solution

  • If I remove the stuff not relevant to the error, here's what we've got:

    generic<class T>
    where T:CAnimal, gcnew()
    ObservableCollection<T>^ GetItems()
    {
        ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
    
        animals->Add(gcnew CDog());
    
        return animals;
    }
    

    Now, what happens if this is invoked as GetItems<CCat^>()? Now you're trying to put a CDog into a list of CCat, and that won't work. That's what the error message is saying: CDog may not be able to be converted to T.

    If you want to do this, there's a couple possible solutions:

    • You could return a list of CAnimal instead. CDog is always valid to put into a list of CAnimal.
    • You could change how you initialize the list of T.

    I'd recommend the latter, and do something like this:

    generic<class T>
    where T:CAnimal, gcnew()
    ObservableCollection<T>^ GetItems(int count)
    {
        ObservableCollection<T>^ animals = gcnew ObservableCollection<T>();
    
        for (int i = 0; i < count; i++)
        {
            T animal = gcnew T();
            animal->Initialize(i, count-i);
            animals->Add(animal);
        }
    
        return animals;
    }
    
    • Because you've got the gcnew() constraint, that means that the class has a zero-parameter constructor, and you're allowed to call it using just type T.
    • Move the constructor parameters you currently have into a method defined on CAnimal. This way you can call it on any T.
    • I removed the enum parameter. You don't need it; you're already specifying which animal you want via the generic.
      • I recommend you do this change regardless of how you implement this. Otherwise, you'll have to consider what things like GetItems<CDog^>(AnimalType::Cat, 3) should do.