This is a text-only version of the following page on https://raymii.org:
---
Title : Cooking with C++ templates and stronger types
Author : Remy van Elst
Date : 13-06-2019
URL : https://raymii.org/s/blog/Cooking_with_Cpp_templates_and_stronger_types.html
Format : Markdown/HTML
---
To gain a better understanding of C++ templates I'm playing around
with them. Most of the online guides stop at the example of a simple template
to, for example, get the max of two inputs, or cover just a bit more (like how
to overload operators for your specific template classes to make `<<` and `+` / `-`
work). Combining templates with a stronger type to pass stuff around led me
to a test kitchen. As in, some code with ingredients, amounts and an oven.
One small thing kept it from working, after some feedback it turned out I was
passing the wrong parameters to the template. Afterwards the error also made
sense. This post covers both my learning and a small piece on stronger types.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
### Stronger typing
Would you rather have a class be clear in its intended usage or would you rather
look up the header/implementation and find out the details in a comment
lingering?
I'm all for the first, so next to playing around with templates, I tried to also
look into stronger typing.
Lets say you have code that deals with `Amounts` as we do here, like `Liters`,
`Milliliters`, `Grams` or `Cubic Liters`. Or, units of measurement (distance),
like `Kilometers`, `Miles`, `Klicks` or `AU's'` if you don't like volume.
One method could be, `pourWater(double water, int duration)`. Are you able to
tell if that's in liters, milliliters, grams or maybe seconds? Probably your
documentation tells you that, but often there is just a comment lingering
somewhere, or you copy example code used earlier.
If the method was `pourWater(Milliliters water, Seconds duration)` it would be
way more clear. I still have more questions, like, how long, what pressure,
where does the water exits the unit etc. But, this is for the sake of example.
The [Fluent C++][1] site has a library for this, `Named Types`. It has all kinds
of advantages, like not having to overload standard operators like `<<`.
There is another article there, [Getting the Benefits of Strong Typing in C++
at a Fraction of the Cost][2]. That's what were doing here, or at least, that
is what I tried to achieve.
Here's my attempt to create these stronger classes:
template
class Amount {
public:
T m_amount;
Amount(T amount) : m_amount(amount) { }
friend std::ostream &operator<<(std::ostream &out, const Amount &amount) {
out << amount.m_amount;
return out;
}
};
template
class Grams : public Amount {
public:
Grams(T amount) : Amount(amount) {}
};
template
class Milliliters : public Amount {
public:
Milliliters(T amount) : Amount(amount) {}
};
By using templates we also elliminate the need to specify the type we're able to
handle. It doesn't matter if I provide my `Grams` as a `double`, `int` or even
`long long`, all will work. You probably do need to make some partial template
specialization to get the correct behaviour, but that outside of the scope of
this example.
You could also still pass `Grams` to something that wants `Milliliters` if that
class accepts any `Amount` as its parameter. If you limit it to `Grams` it will
still accept `Milliliters` due to the inheretance.
If you're worried about overhead, the compiler will probably
optimize it all away to a basic type. And, if you're worried about overhead,
why are you even looking at templates?
### The kitchen
Here's the example code I was cooking up. An ingredient has a name and an amount
and an amount has a unit. Instead of just passing the value as an `int` or
`double`, I wanted to be able to pass the unit itself. For the example I've
used `Milliliters` and `Grams`, which adhere to a base class of `Amount`. In
hindsigt I'm not sure on the name of the base class, since `Unit` or
`Measurement` have also crossed my mind.
The `Ingredient` class takes a name and an `Amount`. The `Oven` class takes two
`Ingredients` and has a `Grill` method to create something delicious. As said
in the above topic, by using specific classes to make the meaning of something
more clear, you emit the need for comments.
### No matching constructor for initialization of Class
You can see the fixed code in the next section. The `Oven` template class:
template
class Oven {
public:
Ingredient m_ingredient1;
Ingredient m_ingredient2;
Oven(Ingredient ingredient1, Ingredient ingredient2) :
m_ingredient1(ingredient1),
m_ingredient2(ingredient2)
I was calling the `Oven` with the following parameters:
Ingredient> Milk {amount_milk, name_milk};
Ingredient> Butter {amount_butter, name_butter};
Oven>, Ingredient>> oven1 {Milk, Butter};
You might already see the problem, I did not however. I kept getting hit with:
No matching constructor for initialization of
'Oven >, Ingredient > >'
After trying different versions of the `Oven` class, different iterations of
the method calling, I was stuck. You know that feeling when you're looking at
the same problem for too long and can't figure it out? I was in that state.
Since templates are new to me I also wasn't sure what to search for anymore.
In my mind, the `Oven` needed its `Ingredients`, which was why I passed them.
I posted my issue online and within 15 minutes received feedback. It turned out,
due to declaring it in the `Oven` constructor as `Ingredient`, I was already
specifying it to be an `Ingredient`, and the
`Oven>` was redundant. Just
`Oven` was enough. With my code, I was giving the class an
`Ingredient>`.
By doing this, coding it up and trying to figure out what's wrong, I find myself
to get a better understanding of the thing I'm learning as to when I just follow
a book. I do need the book, but by actually working on the covered topics I
internalize the knowledge much better.
### Static methods?
If you would make the method `static` (thus being able to allocate it without
declaring a variable), normally you would place the `static` keyword before
the method. If you try that with a template class you'll get an error:
error: a storage class can only be specified for objects and functions
For a template the static keyword is not required. The following:
Ingredient> Beer(Milliliters(30), "Beer");
Ingredient> Whiskey(Milliliters(15), "Whiskey");
works without issues. With the above code it prints:
Ingredient name: Beer, amount: 30
Ingredient name: Whiskey, amount: 15
### The code
This was my example template experiment code, after I fixed the error:
#include
template
class Amount {
public:
T m_amount;
Amount(T amount) : m_amount(amount) {}
friend std::ostream &operator<<(std::ostream &out, const Amount &amount) {
out << amount.m_amount;
return out;
}
};
template
class Grams : public Amount {
public:
Grams(T amount) : Amount(amount) {}
};
template
class Milliliters : public Amount {
public:
Milliliters(T amount) : Amount(amount) {}
};
template
class Ingredient {
public:
Amount m_amount;
std::string m_name;
Ingredient(Amount amount, std::string name) : m_amount(amount),
m_name(name)
{
std::cout << "Ingredient name: " << m_name << ", amount: " << m_amount << "\n";
}
};
template
class Oven {
public:
Ingredient m_ingredient1;
Ingredient m_ingredient2;
Oven(Ingredient ingredient1, Ingredient ingredient2) :
m_ingredient1(ingredient1),
m_ingredient2(ingredient2)
{
std::cout << "Bowl with ingr1: " << m_ingredient1.m_name << ": " <<
m_ingredient1.m_amount << "\n";
std::cout << " ingr2: " << m_ingredient2.m_name << ": " <<
m_ingredient2.m_amount << "\n";
}
void Grill() {
std::cout << "Grilling all ingredients in the oven.\n";
}
};
int main() {
Milliliters amount_water {10};
Milliliters amount_milk {5.5};
Grams amount_flour {5.6};
Grams amount_butter {250};
std::string name_water { "water" };
std::string name_milk { "milk" };
std::string name_flour { "flour" };
std::string name_butter { "butter" };
Ingredient> Milk {amount_milk, name_milk};
Ingredient> Butter {amount_butter, name_butter};
Oven, Grams> oven1 {Milk, Butter};
oven1.Grill();
return 0;
}
[1]: https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/
[2]: https://www.fluentcpp.com/2018/04/06/strong-types-by-struct/
---
License:
All the text on this website is free as in freedom unless stated otherwise.
This means you can use it in any way you want, you can copy it, change it
the way you like and republish it, as long as you release the (modified)
content under the same license to give others the same freedoms you've got
and place my name and a link to this site with the article as source.
This site uses Google Analytics for statistics and Google Adwords for
advertisements. You are tracked and Google knows everything about you.
Use an adblocker like ublock-origin if you don't want it.
All the code on this website is licensed under the GNU GPL v3 license
unless already licensed under a license which does not allows this form
of licensing or if another license is stated on that page / in that software:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Just to be clear, the information on this website is for meant for educational
purposes and you use it at your own risk. I do not take responsibility if you
screw something up. Use common sense, do not 'rm -rf /' as root for example.
If you have any questions then do not hesitate to contact me.
See https://raymii.org/s/static/About.html for details.