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:
#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.
#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;
};
class Derived_1 : public Base
{
public:
Derived_1() = default;
virtual ~Derived_1() = default;
void Print() { std::wcout << L"Print Derived_1" << std::endl; }
};
class Derived_2 : 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; }
};
int Main()
{
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()); 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();
return 0;
}
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.
= = = = = = = = = = = = = =
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
#pragma once
#include <Windows.h>
#include <iostream>
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;
}
};
}
int Main()
{
{
using namespace UseCRTP;
auto pshCRTP = std::make_unique<SuperHero>(L"Oliver",L"Queen",L"Green Arrow");
pshCRTP->ReversePrint();
SuperHero shCRTP {L"Barry",L"Allen",L"The Flash"};
shCRTP.ReversePrint();
}
{
using namespace UseMixin;
auto pshMixin = std::make_unique<Reverse<SuperHero>>(L"Tony",L"Stark",L"Iron Man");
pshMixin->ReversePrint();
using CMixin = Reverse<SuperHero>
CMixin shMixin {L"Miles",L"Morales",L"Ultimate Spider-Man"};
shMixin.ReversePrint();
}
return 0;
}
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:
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).
#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;
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; };
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;
};
}
int main()
{
{
using namespace Messages_vers_01;
Messages email_msg;
CEmail em;
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();
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).
#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;
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; };
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;
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; };
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;
};
}
int main()
{
{
using namespace Messages_vers_02;
Messages email_msg;
CEmail em;
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();
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();
Messages sms_msg;
CSMS sms;
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();
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;
}
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.
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
:
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).
#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;
template <typename T>
class MessagesT : public IMessages, public T
{
public:
MessagesT() = default;
virtual ~MessagesT() = default;
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(); }
};
template<typename... D>
class MessagesHelper :public D...
{
public:
MessagesHelper() = default;
virtual ~MessagesHelper() = default;
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; }
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;
};
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; }
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:
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:
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";
}
}
};
using TextMessage = MessagesT<MessagesHelper<Displayable<CSMS>>>;
using EmailMessage = MessagesT<MessagesHelper<Displayable<CEmail>>>;
}
int main()
{
{
using namespace Messages_vers_03;
Message msg;
TextMessage smsType;
EmailMessage emType;
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);
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);
emType.Send();
emType.SendSpecialized<CEmail>(emType);
emType.Display();
emType.Display<CEmail>(emType, emType.Read());
msg.to = L"My wife (+39)123.123.123";
msg.text = L"I'm still at work. See you later.";
smsType.Compose(msg);
msg.to = L"My new girlfriend (+39)345.345.345";
msg.text = L"Hi, I'm coming to you right now.";
smsType.Compose(msg);
smsType.Send();
smsType.SendSpecialized<CSMS>(smsType);
smsType.Display();
smsType.Display<CSMS>(smsType, smsType.Read());
}
return 0;
}
--> 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:
class CVocal
{
public:
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:
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>>>;
int main()
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());
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());
--> 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