Mentions légales du service

Skip to content
Snippets Groups Projects
Commit ff0c242a authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Use concept syntax directly instead of requires clause.

Concept should anyway be more developed!
parent 0066688e
No related branches found
No related tags found
No related merge requests found
Pipeline #1062800 passed
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Special syntax: typename, template and mandatory this](./3-Syntax.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Special syntax: typename, template and mandatory this](./3-Syntax.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In this chapter, we will review some cases involving templates in which additional syntax are required to help the compiler to do its job; it is useful to have a look because these not that difficult rules might be puzzling if their whereabouts have not been explained a bit. In this chapter, we will review some cases involving templates in which additional syntax are required to help the compiler to do its job; it is useful to have a look because these not that difficult rules might be puzzling if their whereabouts have not been explained a bit.
## Mandatory `this` in calling base class methods ## Mandatory `this` in calling base class methods
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
class Base class Base
{ {
public: public:
Base() = default; Base() = default;
void BaseMethod() void BaseMethod()
{ } { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
class Derived : public Base<T> class Derived : public Base<T>
{ {
public: public:
Derived() = default; Derived() = default;
void DerivedMethod(); void DerivedMethod();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
void Derived<T>::DerivedMethod() void Derived<T>::DerivedMethod()
{ {
BaseMethod(); // Compilation error! BaseMethod(); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Same code without the template argument would be accepted without issue... but with templates the compiler doesn't manage to properly find the base method. Same code without the template argument would be accepted without issue... but with templates the compiler doesn't manage to properly find the base method.
To circumvent this, you must explicitly tell `BaseMethod()` is a method; the most obvious way is to put explicit `this` (which we introduced [here](../2-ObjectProgramming/2-Member-functions.ipynb#The-this-keyword)): To circumvent this, you must explicitly tell `BaseMethod()` is a method; the most obvious way is to put explicit `this` (which we introduced [here](../2-ObjectProgramming/2-Member-functions.ipynb#The-this-keyword)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
void Derived<T>::DerivedMethod() void Derived<T>::DerivedMethod()
{ {
this->BaseMethod(); // Now ok! this->BaseMethod(); // Now ok!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Personally, I even prefer to use an alias which points to the base class: this way it is even clearer where the method (even more so if there are several parent classes). Personally, I even prefer to use an alias which points to the base class: this way it is even clearer where the method (even more so if there are several parent classes).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class T> template<class T>
class Derived2 : public Base<T> class Derived2 : public Base<T>
{ {
public: public:
Derived2() = default; Derived2() = default;
using parent = Base<T>; using parent = Base<T>;
void DerivedMethod() void DerivedMethod()
{ {
parent::BaseMethod(); // also ok! parent::BaseMethod(); // also ok!
} }
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Mandatory `typename` keyword ## Mandatory `typename` keyword
In a `std::vector`, as in several STL constructs, there are several **traits** which are defined: a trait is a type information concerning the class. [This page](https://en.cppreference.com/w/cpp/container/vector) mentions all of them in the `Member types` section. In a `std::vector`, as in several STL constructs, there are several **traits** which are defined: a trait is a type information concerning the class. [This page](https://en.cppreference.com/w/cpp/container/vector) mentions all of them in the `Member types` section.
The first one simply returns the type of the value stored in a `std::vector`, for instance for `std::vector<int>::value_type` is just `int`. The first one simply returns the type of the value stored in a `std::vector`, for instance for `std::vector<int>::value_type` is just `int`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <vector> #include <vector>
{ {
std::vector<int>::value_type i = 5; // convoluted way to just say int i = 5! std::vector<int>::value_type i = 5; // convoluted way to just say int i = 5!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, this simple construct fails in the following case: However, this simple construct fails in the following case:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<ContainerT::value_type>(5) << std::endl; std::cout << static_cast<ContainerT::value_type>(5) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You might see it's just that it was never mentioned `ContainerT` was intended to be a `std::vector`... and you would be right. That's not the full story though: the point of a template function is to just work with any given class that just respects the interface (it's very similar to the [**duck typing**](https://en.wikipedia.org/wiki/Duck_typing) so dear to Pythonists: _If it walks like a duck and it quacks like a duck, then it must be a duck_). You might see it's just that it was never mentioned `ContainerT` was intended to be a `std::vector`... and you would be right. That's not the full story though: the point of a template function is to just work with any given class that just respects the interface (it's very similar to the [**duck typing**](https://en.wikipedia.org/wiki/Duck_typing) so dear to Pythonists: _If it walks like a duck and it quacks like a duck, then it must be a duck_).
The issue in fact here is that the compiler doesn't even know that `value_type` is expected to be a type and not an attribute. So you have to help it with the keyword `typename` just before the type in question (just as indicated by the compiler: fortunately they are now rather helpful in pointing out this kind of issue): The issue in fact here is that the compiler doesn't even know that `value_type` is expected to be a type and not an attribute. So you have to help it with the keyword `typename` just before the type in question (just as indicated by the compiler: fortunately they are now rather helpful in pointing out this kind of issue):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl; std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintFive<int>(); // Won't compile: no trait 'value_type' for an 'int'! PrintFive<int>(); // Won't compile: no trait 'value_type' for an 'int'!
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintFive<std::vector<int>>(); // Ok! PrintFive<std::vector<int>>(); // Ok!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 20 alleviates the need for `typename` keyword in some cases (see for instance this [StackOverflow](https://stackoverflow.com/questions/61990971/why-dont-i-need-to-specify-typename-before-a-dependent-type-in-c20) post with two very interesting links related to that topic); at the moment I must admit I keep putting it everywhere as previously and I am not very knowledgeable when it can be avoided. C++ 20 alleviates the need for `typename` keyword in some cases (see for instance this [StackOverflow](https://stackoverflow.com/questions/61990971/why-dont-i-need-to-specify-typename-before-a-dependent-type-in-c20) post with two very interesting links related to that topic); at the moment I must admit I keep putting it everywhere as previously and I am not very knowledgeable when it can be avoided.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [optional] A bit of wandering: how will C++ 20 concepts will help to enforce the constraint upon the template parameter ### [optional] A bit of wandering: how will C++ 20 concepts will help to enforce the constraint upon the template parameter
Once again, C++ 20 concept will be much appreciated here to provide more straightforward error messages: Once again, C++ 20 concept will be much appreciated here to provide more straightforward error messages:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cppmagics clang %%cppmagics clang
// `cling` C++ interpreter used by kernel doesn't support yet C++ 20 concepts. // `cling` C++ interpreter used by kernel doesn't support yet C++ 20 concepts.
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <list> #include <list>
template<typename T> template<typename T>
concept HasValueType = requires(T) concept HasValueType = requires(T)
{ {
typename T::value_type; typename T::value_type;
}; };
template<class ContainerT> template<HasValueType ContainerT>
requires HasValueType<ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl; std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl;
} }
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
PrintFive<std::vector<int>>(); PrintFive<std::vector<int>>();
PrintFive<std::list<int>>(); PrintFive<std::list<int>>();
PrintFive<int>(); // COMPILATION ERROR! But with a clear error message. PrintFive<int>(); // COMPILATION ERROR! But with a clear error message.
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Mandatory `template` keyword ## Mandatory `template` keyword
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Likewise, compiler sometimes needs help to figure out a template is involved. For instance: Likewise, compiler sometimes needs help to figure out a template is involved. For instance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
struct Foo struct Foo
{ {
template<int N> template<int N>
void PrintN() const; void PrintN() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
template<int N> template<int N>
void Foo::PrintN() const void Foo::PrintN() const
{ {
std::cout << N << std::endl; std::cout << N << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class U> template<class U>
void Print(const U& u) void Print(const U& u)
{ {
std::cout << u.PrintN<0>() << std::endl; std::cout << u.PrintN<0>() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Here the compiler is confused and doesn't understand we mean `PrintN` to be a template method; there is in fact an ambiguity with operators `<` and `>`. These ambiguity cases arises when two templates are involved. Here the compiler is confused and doesn't understand we mean `PrintN` to be a template method; there is in fact an ambiguity with operators `<` and `>`. These ambiguity cases arises when two templates are involved.
To help the compiler figure it out, the solution is very similar to the `typename` one: we specify an explicit template to help the compiler. To help the compiler figure it out, the solution is very similar to the `typename` one: we specify an explicit template to help the compiler.
Fortunately, compilers (at least clang and gcc) became very recently much more helpful and gives away in the error message the probable fix. It wasn't the case until very recently (the "fortunately" at the beginning of this sentence was an "unfortunately" in 2022...); so keep it in mind as it could be quite puzzling the first time you encounter it if the compiler doesn't give you the hint, especially if you're working with a fairly old compiler. Fortunately, compilers (at least clang and gcc) became very recently much more helpful and gives away in the error message the probable fix. It wasn't the case until very recently (the "fortunately" at the beginning of this sentence was an "unfortunately" in 2022...); so keep it in mind as it could be quite puzzling the first time you encounter it if the compiler doesn't give you the hint, especially if you're working with a fairly old compiler.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
template<class U> template<class U>
void Print(const U& u) void Print(const U& u)
{ {
std::cout << u.template PrintN<0>() << std::endl; // Notice the template keyword! std::cout << u.template PrintN<0>() << std::endl; // Notice the template keyword!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: always check obvious template instantiations in test! ## Good practice: always check obvious template instantiations in test!
If you have been very attentive, you might see `Print()` above is faulty: we ask to print on screen a void function... An instantiation reveals the issue: If you have been very attentive, you might see `Print()` above is faulty: we ask to print on screen a void function... An instantiation reveals the issue:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
Foo foo; Foo foo;
Print(foo); // Compilation error! Print(foo); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Without instantiation, the compiler can't easily figure out there is an issue; in `Print()` there are actually implicit conditions on the nature if the template parameter `U`: Without instantiation, the compiler can't easily figure out there is an issue; in `Print()` there are actually implicit conditions on the nature if the template parameter `U`:
* `U` must provide a template `PrintN` method with an integer parameter. * `U` must provide a template `PrintN` method with an integer parameter.
* An overload of operator `<<` must be provided for the return type of this method. * An overload of operator `<<` must be provided for the return type of this method.
`Foo` doesn't check the second condition... but you can't really see it without checking it (once again C++ 20 concepts will help on that matter, but the advise here still stands). `Foo` doesn't check the second condition... but you can't really see it without checking it (once again C++ 20 concepts will help on that matter, but the advise here still stands).
So if the template is intended to work with a `Foo`, it is a good idea to provide a dedicated test that checks it actually works. So if the template is intended to work with a `Foo`, it is a good idea to provide a dedicated test that checks it actually works.
**Attention!** Code coverage tools (at least those we are aware of) won't help you here: a template that is not instantiated isn't considered as code to cover... So you may have a 100 % coverage and still gets a template function or class with a faulty implementation if you never instantiate it. **Attention!** Code coverage tools (at least those we are aware of) won't help you here: a template that is not instantiated isn't considered as code to cover... So you may have a 100 % coverage and still gets a template function or class with a faulty implementation if you never instantiate it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## No virtual template method! ## No virtual template method!
All is in the title here: a method can't be both virtual and template... All is in the title here: a method can't be both virtual and template...
We are speaking specifically of the methods here: a template class may feature virtual (non template) methods without any issue. We are speaking specifically of the methods here: a template class may feature virtual (non template) methods without any issue.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment