Click here to Skip to main content
6,915,647 members and growing! (17,117 online)
Email Password   helpLost your password?
Languages » C++ / CLI » C++/CLI     Beginner

Using generics in C++/CLI

By James T. Johnson, Nishant Sivakumar

Introduces generics and compares it with templates
C++, C++/CLI, Windows, .NETVS2005, Dev
Posted:7 Dec 2004
Views:69,933
Bookmarked:21 times
printPrint Friendly   add Share
      Discuss Discuss   Broken Article?Report  
20 votes for this article.
Popularity: 5.59 Rating: 4.29 out of 5
3 votes, 15.0%
1

2

3

4
17 votes, 85.0%
5

Introduction

Beginning with .NET 2.0, Microsoft has introduced generics into the CLI whereby parameterized types can be declared and used. For C# and VB users, this must have been extremely exciting news; but for C++ people who were already used to templates, this might not have sounded all that interesting. You often hear C++ people ask why they need to use generics when they already have templates. [Note - In C++/CLI, templates can be used both with native and with managed types].

Generics and templates, while similar in nature, are also different in many ways. The most outstanding of these differences is that while templates are implemented by the compiler, generics are instantiated at runtime by the CLR's execution engine (this is possible because generics is directly supported by MSIL). For a detailed write-up on how templates and generics differ, see Brandon Bray's blog entry - Templates and Generics.

This article is not intended to demonstrate how generics are cooler than templates (or even the reverse for that matter), rather it just tries to expose the syntactic usage of generics in C++/CLI. I would like to state here that I found generics rather limited when compared to templates, that too despite not being a core-template guy myself.

Basic syntax

Throughout the article, I'll show some code using templates and then show the equivalent code using generics. Here's a typical native class template.

template<typename T1, typename T2> class NativeData
{
public:
  NativeData(T1 t1)
  {
    m_t1 = t1;
  }
  void DoStuff(T2 t2)
  {
    //...

  }
private:
  T1 m_t1;
};

Now, here's the equivalent in generics :-

generic<typename T1, typename T2> ref class GenericData
{
public:
  GenericData(T1 t1)
  {
    m_t1 = t1;
  }
  void DoStuff(T2 t2)
  {
    //...

  }
private:
  T1 m_t1;
};

Looks just about identical, except that instead of template, the keyword to be used is generic. [Note - generic is one of the 3 new keywords introduced in C++/CLI, the other two being gcnew and nullptr. All other keywords are context sensitive keywords like ref or spaced keywords like for each].

Constraints

See the following template-based class :-

template<typename T> class Native
{
public:
  void Start(int x)
  {
    T* t = new T();
    t->Bark(x);
    t->WagTail();
    delete t;
  }
};

Since templates use lazy constraints, the above code compiles fine (the compiler won't specialize the class template until an instantiation).

And assuming we want to use the class as follows :-

Native<NativeDog> d1;
d1.Start(100);

We need to have a class NativeDog similar to :-

class NativeDog
{
public:
  void Bark(int Loudness)
  {
    Console::WriteLine("NativeDog::Bark {0}",Loudness);
  }
  void WagTail()
  {
    Console::WriteLine("NativeDog::WagTail");
  }
};

As long as the class we are specifying as the template type parameter has the Bark and WagTail methods, it compiles fine. But in generics, we have to specify the constraint (since generics are implemented at runtime). The generic equivalent will be :-

interface class IDog
{
  void Bark(int Loudness);
  void WagTail();
};
generic<typename T> where T:IDog ref class GenRef 
{
public:
  void Start(int x)
  {
    T t = Activator::CreateInstance<T>();
    t->Bark(x);
    t->WagTail();
    delete safe_cast<Object^>(t);    
  }
};

Now that we have specified a constraint for the generic parameter, we can invoke methods on it (as allowed by the constraint). If you are wondering why I had to use the generic overload of Activator::CreateInstance, it's because the compiler won't allow you to gcnew a generic parameter. Similarly, it won't let you delete a generic parameter and hence the safe_cast to Object.

To use the class, we do something like :-

ref class Dog : IDog
{
public:
  void Bark(int Loudness)
  {
    Console::WriteLine("Dog::Bark {0}",Loudness);
  }
  void WagTail()
  {
    Console::WriteLine("Dog::WagTail");
  }
};
GenRef<Dog^> g1;
g1.Start(100);

Generic functions

C++/CLI also supports generic functions (you can have a non-generic class with a generic function or you can have a global generic function). For example :-

generic<typename T> where T:IDog void DoAll(T t)
{
  t->Bark(0);
  t->WagTail();
}

Issues with the constraint mechanism

Doing some things are rather more complicated with generics than with templates.

Example 1

See the following template code :

template<typename T> void NativeIncrement(T t, int x)
{
  t += x;
}

Now, the basic issue with doing this with generics would be that it's difficult to define a constraint that will allow the += operator to work on the generic type parameter. One possible workaround is to define an interface called IIncrementable (similar to the generic collection classes using generic interfaces like IComparable<T>)

interface class IIncrementable
{
  void Add(int);
};
generic<typename T> where T:IIncrementable void RefIncrement(T t, int x)
{
  t->Add(x);  
}

And we can use it on classes that implement IIncrementable :-

ref class MyInt : IIncrementable
{
public:
  MyInt(int n) : m_num(n){}
  void Add(int x)
  {
    m_num += x;
  }
private:
  int m_num;
};

We still cannot use the generic function RefIncrement with simple types like int, float etc. For that, we can use a work-around suggested by Jambo (discussed later in this article).

Example 2

See the following function template :-

template<typename T> T NativeAdd(T t1, T t2)
{
  return t1 + t2;
}

As above, since there is no .NET interface called IAddable, we have to work around the situation. Instead of using an interface like we did last time, we'll use a ref class as constraint - so we can define a CLI-compatible binary + operator.

ref class A1 
{
public:
  A1(int x):m_x(x){}
  static A1^ operator +(const A1^ a1,const A1^ a2)
  {
    return gcnew A1(a1->m_x + a2->m_x);
  }
  int m_x;
};
generic<typename T> where T:A1 T GenericAdd(T t1, T t2)
{
  A1^ tmp = gcnew A1(t1->m_x + t2->m_x);
  return static_cast<T>(tmp);
}

And yeah, all that ugly casting is necessary. Again this generic function will not work on simple types like int, float etc.

Work-around for simple types

Jambo calls this the Adapter Pattern [but neither of us are really sure whether that's what this should be called]. Basically we have a singleton class, NumericalAdapter and a generic interface, INumericalOperations with Add and Subtract methods. The NumericalAdapter class has inner classes, one for each type like IntNumericalAdapter, FloatNumericalAdapter etc. and also provides a static generic method GetNumericalAdapter which returns a INumericalOperations<T> object.

generic<typename T> interface class INumericalOperations
{
  T Add( T t1, T t2 );
  T Subtract( T t1, T t2 );
};
ref class NumericalAdapter
{
private:
  static NumericalAdapter^ m_NumericalAdapter;
  NumericalAdapter(){};
public:
  static NumericalAdapter()
  {
    m_NumericalAdapter = gcnew NumericalAdapter();
  }
private:
  ref class IntNumericalAdapter : INumericalOperations<int>
  {
  public:
    int Add( int t1, int t2 ) 
    { 
      return t1 + t2;
    }
    int Subtract ( int t1, int t2 ) 
    { 
      return t1 - t2;
    }
  };
  ref class FloatNumericalAdapter : INumericalOperations<float>
  {
  public:
    float Add( float t1, float t2 ) 
    { 
      return t1 + t2;
    }
    float Subtract ( float t1, float t2 ) 
    { 
      return t1 - t2;
    }
  };
public:
  generic<typename T> static INumericalOperations<T>^ GetNumericalAdapter()
  {
    Type^ typ = T::typeid;
    if( typ == int::typeid)
    {
      return dynamic_cast<INumericalOperations<T>^>(gcnew IntNumericalAdapter());
    }
    if( typ == float::typeid)
    {
      return dynamic_cast<INumericalOperations<T>^>(gcnew FloatNumericalAdapter());
    }
    return nullptr;
  }
};

And we use the class as follows :-

int i1 = 7, i2 = 23;
int i3 = NumericalAdapter::GetNumericalAdapter<int>()->Add(i1, i2);
float f1 = 5.67f, f2 = 7.13f;
float f3 = NumericalAdapter::GetNumericalAdapter<float>()->Add(f1, f2);

Pretty cool, huh? They don't call Jambo Mister DotNet for nothing.

Conclusion

Generics are not as powerful or flexible as templates are (the intentions behind generics were probably different from that for templates), but if you want your code to be compatible with other CLI languages like C# or VB.NET, you'd be much better off using generics instead of templates wherever possible. Generics can also be used to write generic wrappers over existing C++ template libraries so as to expose them to the rest of the .NET world. As usual, please feel free to submit your criticisms, suggestions and other feedback.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Authors

James T. Johnson


Member
James has been programming in C/C++ since 1998, and grew fond of databases in 1999. His latest interest has been in C# and .NET where he has been having fun writing code starting when v1.0 was in beta 1.

He is currently employed by GrapeCity-Data Dynamics as a Product Manager for Data Dynamics Reports and Data Dynamics Analysis.

Code contained in articles where he is the sole author is licensed via the new BSD license.

Learn more about the products James works with at the CodeProject Catalog
ActiveReports | Data Dynamics Analysis | Data Dynamics Reports
Occupation: Product Manager
Company: GrapeCity, inc.
Location: United States United States

Nishant Sivakumar


Member
Nish (short for Nishant) is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.

Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.

Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.

Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.

Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.
Location: United States United States

Other popular C++ / CLI articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 13 of 13 (Total in Forum: 13) (Refresh)FirstPrevNext
GeneralNoob Troubles PinmemberDraken Nuwar9:00 14 Oct '06  
GeneralRe: Noob Troubles PinmemberBartosz Bien22:01 4 Feb '07  
Generalhttp://active-sport.pl/hotels-in-new-york--casino-hotel-new-new-york-york/ PinsussDebugging Colors Branches5:52 31 May '05  
GeneralNice Article PinmemberHolger Grund6:37 1 Feb '05  
There's so much confusion about generics and templates, it's really a good idea to address this in an article. There are quite couple of things, however, with which I don't agree.

First of all it's worth noting that templates and generics are very similar but also very different concepts. C++/CLI does not reflect the differences in its syntax. A generic parameter type is very different from a template parameter (which is pretty similar to a crafted typedef with some special name lookup considerations). For instance, can't specialize a template with a genpar. The opposite is not true. Again the real type of a genpar is not known until runtime (MSIL->native compilation time).
Both things are conceptually different in where they are supposed to be used. Generics are used to have a rich interface at the binary boundaries (think of .NET Assembly Type description as the evolution of TypeLibs or of ABIs). There could be a (virtually) infinite number of specializations of generic functions or types.
Templates however, are instantiated at runtime. In a C++ program all instantiations are known at build time. Therefore it's possible to generate all specializations and generate code with that additional knowledge.

I don't think I agree with the following:
[..] if you want your code to be compatible with other CLI languages like C# or VB.NET, you'd be much better off using generics instead of templates wherever possible.

It's important to note, that the distinction is only important for the interface contract (types, functions accessible from outside your assembly). There's nothing wrong with exposing template specializations via (generic or non-generic) CLI types.

In fact, I've written a very small demo for a similar presentation where I do pretty much the same as you in your example (well it implements a comparer object via a factory pattern).It demonstrates providing specialized implementations via templation specializations which are tied with the generic interface via some simple TMP trickery. It also supports runtime binding of generics as a fallback mechanism.

-hg
GeneralC++ developers choose PinmemberMajid Shahabfar0:07 9 Dec '04  
GeneralRe: C++ developers choose PinsussArno Nimoy11:31 9 Dec '04  
GeneralRe: C++ developers choose PinmemberNemanja Trifunovic10:46 10 Dec '04  
GeneralRe: C++ developers choose PinmemberNemanja Trifunovic10:37 10 Dec '04  
GeneralSurely that's not all?? PinmemberDon Clugston19:26 8 Dec '04  
GeneralRe: Surely that's not all?? PinstaffNishant S19:37 8 Dec '04  
Generalsecond example PinmemberGoran Mitrovic22:25 7 Dec '04  
GeneralRe: second example PinstaffNishant S22:34 7 Dec '04  
GeneralRe: second example PinmemberHolger Grund6:14 1 Feb '05  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+PgUp/PgDown to switch pages.

PermaLink | Privacy | Terms of Use
Last Updated: 7 Dec 2004
Editor: Nishant Sivakumar
Copyright 2004 by James T. Johnson, Nishant Sivakumar
Everything else Copyright © CodeProject, 1999-2010
Web17 | Advertise on the Code Project