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 $100 credit for 60 days.

### 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.