Click here to Skip to main content
15,180,249 members
Articles / Programming Languages / C++17
Article
Posted 14 Jan 2022

Tagged as

Stats

2.5K views
23 downloads
3 bookmarked

C++/WinRT Runtime Component in Win32 Applications - Part 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
17 Jan 2022CPOL11 min read
Part 2 - Templates and variadic templates
In this article, we will see how our application, thanks to the reusable code, is definitely adaptable to the most varied types of messages. We will also see how and why to use classes/functions templates and variadic templates.

Introduction

  • Variadic Template Class/Function
  • CRTP
  • Mixin

Note: In all the examples, you will find as the first includes the Windows.h file for the simple fact that, since I am using a Microsoft compiler, including that file is defined (among other things) the interfacetype.

This is not strictly necessary, in fact, the examples are written in standard C++ 17 and can be compiled with any C++ 17 compiler, under Windows, Linux, etc.

Therefore, if you are using a non-Microsoft compiler and the keyword interface is not defined, add the following directive:

C++
#include <basetyps.h>

To start, why should these techniques be used? In which cases? What is the purpose? The answer to these questions is linked to one of the cornerstones of the C++ language (and other OOP languages): Inheritance.

As we well know, with inheritance, it is possible to extend the functionality of objects, overwriting existing ones (override) or adding new ones; in other words, starting from a basic object (class), we will derive one or more objects (classes); the new objects in turn can be base classes for other additional objects that derive from them, and so on.

This type of relationship between objects is called Is-a, that is, a class that descends from another is both an object of the derived and base class type (and this for the whole chain). Now, although inheritance is useful in many situations, it often introduces big problems, in some cases multiple inheritance triggers ambiguities that can make it impossible to compile.

A typical example is the Dreaded Diamond Problem (or diamond problem) which introduces ambiguity when a class inherits from two other classes which in turn have a common base class.

Image 1

C++
//Diamond problem, sample and fix

#pragma once
#include <Windows.h>
#include <iostream>

    class Base
    {
    public:

    Base() = default;
    virtual ~Base() = default;

    void Print() { std::wcout << L"Print base" << std::endl; }
    int GetVal() { return m_val; }

    private:
    int m_val = 100;
    };

    //Decomment virtual to fix the problem    
    class Derived_1 : /*virtual*/ public Base
    {
    public:
    Derived_1() = default;
    virtual ~Derived_1() = default;

    void Print() { std::wcout << L"Print Derived_1" << std::endl; }
    };
    
    //Decomment virtual to fix the problem
    class Derived_2 : /*virtual*/ public Base
    {

    public:
    Derived_2() = default;
    virtual ~Derived_2() = default;

    void Print() { std::wcout << L"Print Derived_2" << std::endl; }

    };

    class Derived_3 : public Derived_1, public Derived_2
    {
    public:
    Derived_3() = default;
    virtual ~Derived_3() = default;

    void Print() { std::wcout << L"Print Derived_3" << std::endl; }

    };

    //====================================

    // Main

    int Main()
    {    
    //Ambiguous cast and access sample. 
    //Compilation fails if Base is not declared as virtual
    //in Derived_1 and Derived_2
    
    std::wcout << L" = = = = = = = = = = = = = = " << std::endl;
    std::wcout << L"Ambiguous access sample:" << std::endl;

    auto pDv = std::make_unique<Derived_3>();
   
    Base* pBase = dynamic_cast<Base *>(pDv.get()); //<-- Not compile or warning
    pBase->Print();
    int val = pBase->GetVal();

    Derived_1* pDv1 = dynamic_cast<Derived_1*>(pDv.get());
    pDv1->Print();
    val = pDv1->GetVal();

    Derived_2* pDv2 = dynamic_cast<Derived_2*>(pDv.get());
    pDv2->Print();
    val = pDv2->GetVal();
    
    pDv->Print();
    val = pDv->GetVal(); //<-- Not compile
       
    return 0;
    }
                               
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//Compiler Output:
//warning C4540: dynamic_cast used to convert to inaccessible or ambiguous base; 
//run-time test will fail ('Derived_3 *' to 'Base *')                              
//error C2385: ambiguous access of 'GetVal'
//message : could be the 'GetVal' in base 'Base'

By decommenting the virtual keyword in the definitions of Derived_1 and Derived_2, the problem will be solved, as only one copy of Base will be implemented.

C++
//Output:

 = = = = = = = = = = = = = =
Ambiguous access sample:
Print base
Print Derived_1
Print Derived_2
Print Derived_3

As we have just seen, multiple inheritance is potentially dangerous. But then, to reduce the side effects of multiple heredity, how can we do? Well, we have various techniques at our disposal:

  • Composition
  • Aggregation
  • CRTP
  • Mixin

Note: Composition means the creation of complex objects composed of one or more simpler objects, the classic example of a car that is considered a single object, while in reality it is composed of various objects: the engine, the steering wheel, the wheels, the screws, etc.

The relationship in the case of composition is of type Has-a. The complex object that contains all the others will have to handle the simplest objects, in other words, the objects contained can only exist as a function of the object that contains them.

Aggregation

Aggregation always refers to the creation of complex objects composed of one or more simpler objects, but in this case, the complex object does not handle the aggregate objects that instead live independently of the object that contains them.

Also in this case, the relationship between objects is of type Has-a.

CRTP

CRTP is used for the purpose of adding generic functionality to a class. We have already seen an introduction to CRTP in Part 1.

Mixin

Mixin is also used for the purpose of adding generic functionality to a class. A Mixin class, however, derives from the type of object to which we want the functionality of the mixin class to be added CRTP and Mixin, often, are used in the form of variadic templates.

Note: Variadic template is a template class or function that accepts an arbitrary number of arguments. We saw an introduction to variadic templates in the first part of this tutorial about the WinRT runtime classes implemented (for us) by Visual Studio 2019.

In the CRTP vs Mixin example below, we will see how in the end, in output, the final result will be identical both following the CRTP and Mixin pattern. Keep in mind that the purpose, in both cases, is to extend a given class by adding new features, trying to avoid the traps of multiple inheritance.

We will equip with a ReversePrint() function a class that already has its own PrintData() print function. The Reverse class in which the ReversePrint() method is implemented is a template class that adheres to the CRTP idiom in the UseCRTP namespace and the Mixin idiom in the UseMixin namespace. Below, we will see in more detail the differences between the two patterns.

CRTP vs Mixin

Sample

C++
#pragma once
#include <Windows.h>
#include <iostream>

//CRTP
namespace UseCRTP
{
  template <typename T>
  class Reverse 
  {
    public:		
		
    Reverse() = default;
    virtual ~Reverse() = default;
	
    void ReversePrint() 
    {			

    static_cast<T&>(*this).PrintData();
		
    std::wstring fName = static_cast<T&>(*this).GetFirstName();
    std::wstring lName = static_cast<T&>(*this).GetLastName();
    std::wstring alias = static_cast<T&>(*this).GetAlias();
			
    std::wcout << L"Reverse Print (CRTP):" 
	       << std::endl
		   << alias << L" :"
		   << lName << L", " 
		   << fName  
		   << std::endl << std::endl;
    }		
  };
	
class SuperHero :public Reverse<SuperHero>
{
    public:

    SuperHero() = default;
    virtual ~SuperHero() = default;

    SuperHero(std::wstring fName, std::wstring lName, std::wstring alias)
	{
		m_FirstName = fName;
		m_LastName = lName;
		m_Alias = alias;
	}

	void PrintData() { std::wcout <<L"Regular Print:" << std::endl
			                      << m_FirstName  << L", " 
			                      << m_LastName   << L" :" 
			                      << m_Alias      << std::endl
			                      << std::endl; }
		
	std::wstring GetFirstName() { return m_FirstName;}
	std::wstring GetLastName()  { return m_LastName;}
	std::wstring GetAlias()     { return m_Alias;}
    
        void SetFirstName(std::wstring const& fName) {  m_FirstName = fName; }
	void SetLastName(std::wstring const& lName)  { m_LastName = lName; }
	void SetAlias(std::wstring const& alias)     { m_Alias = alias; }

	private:
		std::wstring m_FirstName = L"";
		std::wstring m_LastName  = L"";
		std::wstring m_Alias     = L"";
	};
}

//========================================

namespace UseMixin
{

class SuperHero
{
    public:

    SuperHero() = default;
    virtual ~SuperHero() = default;

    SuperHero(std::wstring fName, std::wstring lName, std::wstring alias)
	{
		m_FirstName = fName;
		m_LastName = lName;
		m_Alias = alias;
	}

	void PrintData() { std::wcout <<L"Regular Print:" << std::endl
			                      << m_FirstName  << L", " 
			                      << m_LastName   << L" :" 
			                      << m_Alias      << std::endl
			                      << std::endl; }
		
	std::wstring GetFirstName() { return m_FirstName;}
	std::wstring GetLastName()  { return m_LastName;}
	std::wstring GetAlias()     { return m_Alias;}
    
        void SetFirstName(std::wstring const& fName) {  m_FirstName = fName; }
	void SetLastName(std::wstring const& lName)  { m_LastName = lName; }
	void SetAlias(std::wstring const& alias)     { m_Alias = alias; }

	private:
		std::wstring m_FirstName = L"";
		std::wstring m_LastName  = L"";
		std::wstring m_Alias     = L"";
	};
}
	
template<typename T>
class Reverse :public T
 {
		
public:
    Reverse() = default;
    virtual ~Reverse() = default;

    Reverse<T>(std::wstring fName, std::wstring lName, std::wstring alias )
    {
      T::SetFirstName(fName);
      T::SetLastName(lName);
      T::SetAlias(alias);
    }
		
    void ReversePrint() 
    {
      T::PrintData();			
			
      std::wcout << L"Reverse Print (Mixin):"
      << std::endl
      << T::GetAlias() << L" :"
      << T::GetLastName() << L", "
      << T::GetFirstName()
      << std::endl << std::endl;				
    }	
 };
}

//========================================
//Main

int Main()
{
    //CRTP
    {
        using namespace UseCRTP;

        //We can use smart pointers        
        auto pshCRTP = std::make_unique<SuperHero>(L"Oliver",L"Queen",L"Green Arrow");
        pshCRTP->ReversePrint();

        //Or plain old 
        SuperHero shCRTP {L"Barry",L"Allen",L"The Flash"};
        shCRTP.ReversePrint();
    }

    //Mixin
    {       
        using namespace UseMixin;
       
        //We can use smart pointers
        auto pshMixin = std::make_unique<Reverse<SuperHero>>(L"Tony",L"Stark",L"Iron Man");
        pshMixin->ReversePrint();

        //Or plain old
        using CMixin = Reverse<SuperHero>
        CMixin shMixin {L"Miles",L"Morales",L"Ultimate Spider-Man"};
        shMixin.ReversePrint();
    }

    return 0;
}

//Output:

Regular Print:
Oliver, Queen :Green Arrow

Reverse Print (CRTP):
Green Arrow :Queen, Oliver

Regular Print:
Barry, Allen :The Flash

Reverse Print (CRTP):
The Flash :Allen, Barry

Regular Print:
Tony, Stark :Iron Man

Reverse Print (Mixin):
Iron Man :Stark, Tony

Regular Print:
Miles, Morales :Ultimate Spider-Man

Reverse Print (Mixin):
Ultimate Spider-Man :Morales, Miles

Main differences between CRTP and Mixin in the example:

  • CRTP: The SuperHero class inherits the Reverse<T> template class.
  • Mixin: The SuperHero class inherits nothing and remains unchanged while the template class inherits the class of its argument (T).
  • CRTP: You need a cast between argument T of the template class and the template class itself.
  • Mixin: No cast conversion is necessary.
  • CRTP: No additional specialized constructors need to be implemented in the template class.
  • Mixin: (In this case) you need to implement a specialized builder.
  • CRTP: In main, the SuperHero class is used directly.
  • Mixin: In main, the SuperHero class is the argument of the Reverse template class.

When to use CRTP and when Mixin then? We will use CRTP when we want to implement new generic features by extending a given class by means of inheritance. The class to be extended must in fact derive from the template class and the template class must have as its argument the derived class itself.

We will use Mixin when we want to equip a given class with additional features but we want the classes involved to remain independent. The class to be extended must not derive from the template class; the template class must derive from the class of your topic.

All Together Now

We conclude the brief summary on some features of C++ with an example on the variadic template classes and functions. Below we will see how, using a mixture of CRTP, Mixin and variadic template, it is possible to extend classes to our liking in an alternative way compared to the "classic" inheritance.

The most attentive will have at this point already grasped the intrinsic contradiction: to limit the problems of inheritance we will use ... inheritance (although in a different way).

Variadic Template Class and Function Sample

I have previously introduced variadic templates; as mentioned, these are classes or template functions that support any number of arguments. This feature introduces great flexibility in developing even very complex applications.

Sample app: first version. (the customer is always right... perhaps)

Suppose we have been commissioned by one of our clients to create an application that sends emails to his customers. The features required during the project are:

  • Compose emails
  • Send them

Suppose also that we, after accepting the assignment, have raised the right objections about how a correct design of the application would need many other features: let's assume a database from which to take the data of the recipients, a system of saving and displaying the same, etc.

Finally, suppose that our client did not want to hear reasons and insisted (and paid) to have only the two features described above. Nothing else. That being the case, we write our application as it wishes, (after all, how do you say? The customer is always right...) and we give it to him on trial. Below is the code of our application (first version).

C++
#include <Windows.h>
#include <iostream>
#include <vector>

namespace Messages_vers_01
{
	interface IMessages
	{
		struct __tagMESSAGES
		{
			std::wstring to;
			std::wstring subject;
			std::wstring text;
		};
		using Message = __tagMESSAGES;		
		virtual void Compose(const Message& msg) = 0;
		virtual void Send() = 0;
		virtual std::vector<Message> Read() = 0;
    };

    using Messages = IMessages::Message;

    interface IDisplay
    {
        virtual void Display() = 0;
    };

    class CEmail :public IMessages, public IDisplay
    {

    public:
    CEmail() = default;
    virtual ~CEmail() = default;

    //IMessages
    virtual void Compose(const Message& msg) { m_email.push_back(msg); };
    virtual void Send() { std::wcout << std::endl << L"  Email sent! " << std::endl; };
    virtual std::vector<Message> Read() { return m_email; };

    //IDisplay
    void Display()
    {

    std::vector<Messages>emails_sent = Read();

    std::wcout << std::endl;
    std::wcout  << L" * * * * * * * * * * *"  << std::endl;

    for (size_t i = 0; i < emails_sent.size(); i++)
    {
    std::wcout  << L"\n\r";
    std::wcout  << L"  # "  << (i + 1)  << std::endl;
    std::wcout  << L"  To:     \t"  << emails_sent[i].to.c_str()  << std::endl;
    std::wcout  << L"  Subject:\t"  << emails_sent[i].subject.c_str()  << std::endl;
    std::wcout  << L"  Text:   \t"  << emails_sent[i].text.c_str()  << std::endl;
    std::wcout  << L"\n\r";
    }

    std::wcout  << L" * * * * * * * * * * *"  << std::endl;
    }

    private:
    std::vector<Message> m_email;
    };
  }

    //=====================================

    //Main
    int main()
    {
    {
    using namespace Messages_vers_01;

    Messages email_msg;
    CEmail em;

    //#1
    email_msg.to = L"my_best_client@myclient.com";
    email_msg.subject = L"Hey, how you doing?";
    email_msg.text = L"Hello my best client! Greetings.";

    em.Compose(email_msg);
    em.Send();

    //#2
    email_msg.to = L"next_client@myclient.com";
    email_msg.subject = L"A word to my NEXT best client!";
    email_msg.text = L"Hello my next best client! Do you know we are in your zone?.";

    em.Compose(email_msg);
    em.Send();

    em.Display();

    return 0;
    }
//Output

    Email sent!

    Email sent!

 * * * * * * * * * * *

  # 1
  To:           my_best_client@myclient.com
  Subject:      Hey, how you doing?
  Text:         Hello my best client! Greetings.

  # 2
  To:           next_client@myclient.com
  Subject:      A word to my NEXT best client!
  Text:         Hello my next best client! Do you know we are in your zone?

 * * * * * * * * * * *

Nothing spectacular, the CEmail class implements the IMessages and IDisplay interfaces. Note: In the IMessages and IDisplay interfaces, pure virtual functions are declared; for those who do not remember, a pure virtual function MUST be implemented in classes that derive from abstract classes and/or that implement interfaces in which pure virtual functions are declared. The customer is happy and after some time, he instructs us to add to the application the possibility of sending, in addition to emails, SMS messages. We run and implement the new functionality... incorrectly. Note: The application certainly works, but there are design errors. Below the code of our application (second version).

C++
#pragma once
#include <Windows.h>
#include <iostream>
#include <vector>

namespace Messages_vers_02
{

interface IMessages
	{
		struct __tagMESSAGES
		{
			std::wstring to;
			std::wstring subject;
			std::wstring text;
		};
		using Message = __tagMESSAGES;		
		virtual void Compose(const Message& msg) = 0;
		virtual void Send() = 0;
		virtual std::vector<Message> Read() = 0;
	};
	
	using Messages = IMessages::Message;

	interface IDisplay
	{
		virtual void Display() = 0;
	};

	class CSMS :public IMessages, public IDisplay
	{	
	 public:
		CSMS() = default;
		virtual ~CSMS() = default;

		//IMessages
		virtual void Compose(const Message& msg) { m_sms.push_back(msg); };
		virtual void Send() { std::wcout << std::endl << L"   SMS sent! " << std::endl; };
		virtual std::vector<Message> Read() { return m_sms; };

		//IDisplay
		void Display()
		{
			std::wcout << std::endl;
			std::wcout << L" * * * * * * * * * * *" << std::endl;

			for (size_t i = 0; i < m_sms.size(); i++)
			{
				std::wcout << L"\n\r";
				std::wcout << L"  # " << (i + 1) << std::endl;
				std::wcout << L"  To:     \t" << m_sms[i].to.c_str() << std::endl;
				std::wcout << L"  Subject:\t" << m_sms[i].subject.c_str() << std::endl;
				std::wcout << L"  Text:   \t" << m_sms[i].text.c_str() << std::endl;
				std::wcout << L"\n\r";
			}

			std::wcout << L" * * * * * * * * * * *" << std::endl;
		}

	private:
		std::vector<Message> m_sms;
	};

	class CEmail :public IMessages, IDisplay
	{

	public:
		CEmail() = default;
		virtual ~CEmail() = default;

		//IMessages
		virtual void Compose(const Message& msg) { m_email.push_back(msg); };
		virtual void Send() { std::wcout << std::endl << L"  Email sent! " << std::endl; };
		virtual std::vector<Message> Read() { return m_email; };

		//IDisplay
		void Display()
		{		
			std::wcout << std::endl;
			std::wcout << L" * * * * * * * * * * *" << std::endl;

			for (size_t i = 0; i < m_email.size(); i++)
			{
				std::wcout << L"\n\r";
				std::wcout << L"  # " << (i + 1) << std::endl;
				std::wcout << L"  To:     \t" << m_email[i].to.c_str() << std::endl;
				std::wcout << L"  Subject:\t" << m_email[i].subject.c_str() << std::endl;
				std::wcout << L"  Text:   \t" << m_email[i].text.c_str() << std::endl;
				std::wcout << L"\n\r";
			}

			std::wcout << L" * * * * * * * * * * *" << std::endl;
		}

	private:
		std::vector<Message> m_email;
	};
}

//Main
int main()
{
    {
           using namespace Messages_vers_02;

           //Emails section

           Messages email_msg;
           CEmail em;

           //#1          
           email_msg.to = L"my_best_client@myclient.com";
           email_msg.subject = L"Hey, how you doing?";
           email_msg.text = L"Hello my best client! Greetings.";

           em.Compose(email_msg);
           em.Send();

           //#2           
           email_msg.to = L"next_client@myclient.com";
           email_msg.subject = L"A word to my NEXT best client!";
           email_msg.text = L"Hello my next best client! Do you know we are in your zone?";

           em.Compose(email_msg);
           em.Send();           
           
           em.Display();

           //=============================================

           //SMSs section

           Messages sms_msg;
           CSMS sms;

           //#1          
           sms_msg.to = L"My wife (+39)123.123.123";           
           sms_msg.text = L"I'm still at work. See you later.";

           sms.Compose(sms_msg);
           sms.Send();

           //#2           
           sms_msg.to = L"My new girlfriend (+39)345.345.345";          
           sms_msg.text = L"Hi, I'm coming to you right now.";

           sms.Compose(sms_msg);
           sms.Send();           
           
           sms.Display();
       }
       
return 0;
}

//Output

  Email sent!

  Email sent!

 * * * * * * * * * * *

  # 1
  To:           my_best_client@myclient.com
  Subject:      Hey, how you doing?
  Text:         Hello my best client! Greetings.

  # 2
  To:           next_client@myclient.com
  Subject:      A word to my NEXT best client!
  Text:         Hello my next best client! Do you know we are in your zone?

 * * * * * * * * * * *

   SMS sent!

   SMS sent!

 * * * * * * * * * * *

  # 1
  To:           My wife (+39)123.123.123
  Text:         I'm still at work. See you later.


  # 2
  To:           My new girlfriend (+39)345.345.345
  Text:         Hi, I'm coming to you right now.

 * * * * * * * * * * *

Here too nothing of that, we have added a new class to the project and in this, we have implemented the usual IMessages and IDisplay interfaces.

In doing so, we followed the same initial design scheme. Unfortunately, however, in adding the new functionality, we did what should never be done: we duplicated our code, in fact the CSMS and CEmail classes are identical and even the Display method contains practically the same code in both classes.

Following this scheme, when we have to add new features, for each of them, we will rewrite the same code again and again.

In a short time, our code will become cyclopean, in practice not maintainable. Something is wrong with our project.

In the meantime, our hypothetical application has sold quite a few copies and another client instructs us to extend its functionality by adding the possibility of sending not only emails and SMS but also voice messages and images.

Studying the case, we note that the two new features are nothing more than two additional types of messages. We need, at this point, a generic mechanism that implements any type of message without having to take back the same code every time. These are the types of problems that are solved with template classes and functions.

At this point, however, realizing that the original project does not support any template, we can only review the structure of our application from scratch. This time, we will design the app relying on classes and template functions.

In the new project, we want each type (class) of message to remain independent of the others, but that all types of messages have a common manager for similar functions (Send, Read, etc.) that takes care of delegating to more specialized functions, for each message class, the work we intend to make it do.

To give an example, each message will necessarily have to manage a different sending function. This is natural, the code to send emails will be different from the code for sending SMS.

Therefore, the generic handler (a template class) will provide a generic method of sending but can also call the specific method of sending, depending on whether it is emails or SMSs (and other types in the future). We redesign and rewrite the application according to the following UML scheme.

Image 2

As we see in the diagram, we implement the IMessages interface only in the (basic) MessagesT template class; the latter is a template class that in addition to implementing IMessages inherits the class of its argument (T). The argument (T) of it (from project) is the variadic template class MessageHelper. As we can see in the code (below), the MessageHelper class inherits the class of its template argument (D).

We can also see the syntax for defining a variadic template class (typename...). The MessageHelper class will provide functions that are common to all message types: Compose(), Send(), and Read().

However, as mentioned before, if the Send function of the generic Class MessageHelper can be invoked when a hypothetical Send function common to multiple message types is needed, we will surely also need to call the specialized version of Send specific to each message class.

We achieve this by adding to the MessageHelper class a variadic template function whose name is SendSpecialized. We can see the syntax of the variadic template function SendSpecialized:

C++
template <typename I, typename... Args>
void SendSpecialized(I i, Args... args)
{
 I::Send(args...);
}
//......
//......

We are simply declaring a template function with multiple arguments of which the first is I and the other (Args...) (or rather the others) are arbitrary. Args..., in the definition of the function, is the parameter pack and can accept any number of arguments. In the body of the function, we find the args argument of the function with the triple point operator (...) representing the parameter pack expansion.

This operator (...) unpacks/expands the parameter pack in comma-separated arguments and then for each argument, calls the Function Send(args...) recursively. We see that as in the case of the variadic template classes, also the variadic functions use the operator (...) called ellipsis.

We apply the same logic to the variadic template Displayable class that will equip the various message classes as well as the specific Display function, with a generic Display() function.

Once this is done, we declare the classes for the first two types of messages: CEmail and CSMS.

Below is the code of our application (third version).

C++
#pragma once
#include <Windows.h>
#include <iostream>
#include <vector>

namespace Messages_vers_03
{
	interface IMessages
	{
		struct __tagMESSAGES
		{
			std::wstring to;
			std::wstring subject;
			std::wstring text;
		};

		using Message = IMessages::__tagMESSAGES;

		virtual void Compose(const Message& msg) = 0;
		virtual void Send() = 0;
		virtual std::vector<Message< Read() = 0;
	};

	using Message = IMessages::Message;

    //Mixin template class	
    template <typename T>
	class MessagesT : public IMessages, public T
	{
	public:

		//default ctor/dtor
		MessagesT() = default;
		virtual ~MessagesT() = default;

		//IMessages
		virtual void Compose(const Message& msg) override { T::Compose(msg); }
		virtual void Send()  override { T::Send(); }
		virtual std::vector<Message> Read() override { return T::Read(); }
	};

    //Mixin variadic template class
	template<typename... D>
	class MessagesHelper :public D...
	{
	public:

		MessagesHelper() = default;
		virtual ~MessagesHelper() = default;

		//IMessages
		void Compose(const Message& msg) { m_msg.push_back(msg); }		
		void Send() { std::wcout << std::endl; 
                      std::wcout << L"--> Generalized send method executed..." << std::endl; }
		std::vector<Message> Read() { return m_msg; }

		//variadic template function
		template <typename I, typename... Args>
		void SendSpecialized(I i, Args... args)
		{			
			I::Send(args...);
		}

	private:
		std::vector<Message> m_msg;
	};

	interface IDisplay
	{
		virtual void Display() = 0;
		virtual void Display(const std::vector<Message>& msg) = 0;
	};

    //Mixin variadic template class
	template <typename... J>
	class Displayable : public IDisplay, public J...
	{
	public:
		Displayable() = default;
		virtual ~Displayable() = default;

		virtual void Display() override { std::wcout << std::endl;  
                std::wcout << L"--> Generalized Display method executed" << std::endl; }
		
		virtual void Display(const std::vector<Message>& msg) override 
        { std::wcout << std::endl;  std::wcout << L"--> Generalized Display method executed" 
          << std::endl; }

		//variadic template function
        template <typename V,typename...Args>
		void Display(V v, Args... args)
		{
			
			std::wcout << std::endl;
			std::wcout << L" * * * * * * * * * * *" << std::endl;
			
			V::Display(args...);
			

			std::wcout << L" * * * * * * * * * * *" << std::endl;
			std::wcout << std::endl;
		}
	};

	class CEmail
	{
	public:

		//default ctor/dtor
		CEmail() = default;
		virtual ~CEmail() = default;

		void Send() { std::wcout << std::endl; std::wcout << L"--> 
                      Specialized email send method executed..." << std::endl; }

		void Display(const std::vector<Message>& msg) 
		{ 
			std::wcout << std::endl;
			std::wcout << L"--> Specialized email display method executed..." << std::endl; 

			for (size_t i = 0; i < msg.size(); i++)
			{
				std::wcout << L"\n\r";
				std::wcout << L"  # " << (i + 1) << std::endl;
				std::wcout << L"  To:     \t" << msg[i].to.c_str() << std::endl;
				std::wcout << L"  Subject:     \t" << msg[i].subject.c_str() << std::endl;
				std::wcout << L"  Text:   \t" << msg[i].text.c_str() << std::endl;
				std::wcout << L"\n\r";
			}
		}
	};

	class CSMS 
	{
	public:

		//default ctor/dtor
		CSMS() = default;
		virtual ~CSMS() = default;

		void Send() { std::wcout << std::endl; std::wcout << 
                      L"--> Specialized sms send method executed..." << std::endl; }

		void Display(const std::vector<Message>& msg) 
		{ 
			std::wcout << std::endl;
			std::wcout << L"--> Specialized sms display method executed..." << std::endl; 

			for (size_t i = 0; i < msg.size(); i++)
			{
				std::wcout << L"\n\r";
				std::wcout << L"  # " << (i + 1) << std::endl;
				std::wcout << L"  To:     \t" << msg[i].to.c_str() << std::endl;
				std::wcout << L"  Text:   \t" << msg[i].text.c_str() << std::endl;
				std::wcout << L"\n\r";
			}
		}		
	};

    //New message types declaration
    using TextMessage = MessagesT<MessagesHelper<Displayable<CSMS>>>;
    using EmailMessage = MessagesT<MessagesHelper<Displayable<CEmail>>>;
}

//Main

int main()
{
 {
    using namespace Messages_vers_03;
 
    Message msg;
    
    TextMessage smsType;
    EmailMessage emType;
    
    //Email message type
    
    //# 1 email type  message
    msg.to = L"my_best_client@myclient.com";
    msg.subject = L"Hey, how you doing?";
    msg.text = L"Hello my best client! Greetings.";

    emType.Compose(msg);

    //# 2 email type message
    msg.to = L"next_client@myclient.com";
    msg.subject = L"A word to my NEXT best client!";
    msg.text = L"Hello my next best client! Do you know we are in your zone?";
          
    emType.Compose(msg);
   
   //performs generic send method,  optional to specialized method,
   //it is not necessary here
    emType.Send();
    
    emType.SendSpecialized<CEmail>(emType);

   //performs generic display method,  optional to specialized method,
   //it is not necessary here   
    emType.Display();
    
    emType.Display<CEmail>(emType, emType.Read());
    
 //==========================================

    //Text message type

     //# 1 text type  message
    msg.to = L"My wife (+39)123.123.123";
    msg.text = L"I'm still at work. See you later.";
    
    smsType.Compose(msg);

    //# 2 text type message
    msg.to = L"My new girlfriend (+39)345.345.345"; 
    msg.text = L"Hi, I'm coming to you right now.";

    smsType.Compose(msg);

    //performs generic send method,  optional to specialized method,
    //it is not necessary here    
    smsType.Send();

    smsType.SendSpecialized<CSMS>(smsType);

    //performs generic display method,  optional to specialized method,
    //it is not necessary here
    smsType.Display();

    smsType.Display<CSMS>(smsType, smsType.Read());
 }

return 0;
}

//Output:
    --> Generalized send method executed...
    --> Specialized email send method executed...
    --> Generalized Display method executed
    * * * * * * * * * * *
    --> Specialized email display method executed...

    # 1
    To:           my_best_client@myclient.com
    Subject:      Hey, how you doing?
    Text:         Hello my best client! Greetings.

    # 2
    To:           next_client@myclient.com
    Subject:      A word to my NEXT best client!
    Text:         Hello my next best client! Do you know we are in your zone?

    * * * * * * * * * * *

    --> Generalized send method executed...
    --> Specialized sms send method executed...
    --> Generalized Display method executed
    * * * * * * * * * * *
    --> Specialized sms display method executed...

    # 1
    To:           My wife (+39)123.123.123
    Text:         I'm still at work. See you later.

    # 2
    To:           My new girlfriend (+39)345.345.345
    Text:         Hi, I'm coming to you right now.

    * * * * * * * * * * *

We can see how in this new version of the app, we have, on the one hand, avoided the problem of multiple inheritance, while keeping it in another form and how some functions are still polymorphic. In addition, the introduction of variadic templates in the project makes it easier to understand any new types of messages over time. In fact, we said before that it was necessary to rewrite the (fictional) app precisely because we had been asked to add other types of messages (voice and images). This time, however, adding the new features is a 5-minute job. We add two more classes to the project:

C++
//.........
//.........

class CVocal
	{

	public:
		
		//default ctor/dtor
		CVocal() = default;
		virtual ~CVocal() = default;

		void Send() { std::wcout << std::endl; std::wcout << L"--> 
                      Specialized vocal send method executed..." << std::endl; }

		void Display(const std::vector<Message>& msg)
		{
			std::wcout << std::endl;
			std::wcout << L"--> Specialized vocal display method executed..." << std::endl;

			for (size_t i = 0; i < msg.size(); i++)
			{
				std::wcout << L"\n\r";
				std::wcout << L"  # " << (i + 1) << std::endl;
				std::wcout << L"  To:     \t" << msg[i].to.c_str() << std::endl;
				std::wcout << L"  Audio:  \t" < msg[i].audio_video_image.c_str() << std::endl;
				std::wcout << L"\n\r";
			}
		}
	};

	class CVideoOrImage
	{
	public:

		//default ctor/dtor
		CVideoOrImage() = default;
		virtual ~CVideoOrImage() = default;

		void Send() { std::wcout << std::endl; std::wcout << L"--> 
                      Specialized image or video send method executed..." << std::endl; }

		void Display(const std::vector<Message>& msg)
        {
           std::wcout << std::endl;
           std::wcout << L"--> Specialized image or video display method executed..." 
                               << std::endl;

           for (size_t i = 0; i < msg.size(); i++)
           {
                 std::wcout << L"\n\r";
                 std::wcout <<L"  # " << (i + 1) << std::endl;
                 std::wcout << L"  To:     \t" << msg[i].to.c_str() << std::endl;
                 std::wcout << L"  Image:  \t" << msg[i].audio_video_image.c_str() 
                                   << std::endl;
                  std::wcout << L"\n\r";
            }
         }
    };

    //.....
    //.....
     using VocalMessage = MessagesT<MessagesHelper<Displayable<CVocal>>>;
     using VideoOrImage = MessagesT<MessagesHelper<Displayable<CVideoOrImage>>>;

    //.....
    //.....

//Main
int main()

//.....
//.....

 //Vocal Messages

    VocalMessage vmType;
    msg.to = L"(+39)123.123.123";
    msg.audio_video_image = L"vocal.mp4";
    
    vmType.Compose(msg);

    vmType.SendSpecialized<CVocal>(vmType);

    vmType.Display<CVocal>(vmType, vmType.Read());

    //Image Messages

    VideoOrImage vd_imType;
    msg.to = L"(+39)123.123.123";
    msg.audio_video_image = L"image.png";

    vd_imType.Compose(msg);

    vd_imType.SendSpecialized<CVideoOrImage>(vd_imType);

    vd_imType.Display<CVideoOrImage>(vd_imType, vd_imType.Read());

//.....
//.....

//Output:
--> Specialized vocal send method executed...
 * * * * * * * * * * *
--> Specialized vocal display method executed...

  # 1
  To:           (+39)123.123.123
  Audio:        vocal.mp4

 * * * * * * * * * * *

--> Specialized image or video send method executed...
 * * * * * * * * * * *
--> Specialized image or video display method executed...

  # 1
  To:           (+39)123.123.123
  Image:        image.png

 * * * * * * * * * * *

Well, now our application, thanks to the reusable code, is definitely adaptable to the most varied types of messages. We have also seen how and why to use classes/functions templates and variadic templates; we can therefore consider concluded this brief recap of some important features of C++ and continue the sample UWP/WinRT/Win32 application.

Go to Part 3: Implementing the demo C++ WinRT/Win32 Application.

History

  • 14th January, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Giuseppe Pischedda
Software Developer (Senior)
Italy Italy
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA14-Jan-22 21:49
professionalȘtefan-Mihai MOGA14-Jan-22 21:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

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