Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Ashok Kumar, Bharath
so2021
Commits
c042b840
Commit
c042b840
authored
Jul 06, 2021
by
Praetorius, Simon
Browse files
New presentation slides for template specialization and concepts added
parent
a8748828
Changes
5
Hide whitespace changes
Inline
Side-by-side
presentation/_includes/concepts.md
0 → 100644
View file @
c042b840
---
class
:
center, middle
# Constraints and concepts
---
# Constraints and concepts
-
Not all algorithms can be specialized with types
-
Sometimes we need type
*categories*
, type
*properties*
, or type
*concepts*
## Example - Euclidean distance
```
c++
template
<
typename
Point
>
double
distance
(
Point
const
&
a
,
Point
const
&
b
)
{
double
result
=
0.0
;
for
(
std
::
size_t
i
=
0
;
i
<
a
.
size
();
++
i
)
result
+=
(
a
[
i
]
-
b
[
i
])
*
(
a
[
i
]
-
b
[
i
]);
return
std
::
sqrt
(
result
);
}
```
### Requirements
-
`Point`
must be array-like, i.e.,
`operator[](int)`
and
`size()`
function
-
value type of
`Point`
should be (convertible to)
`double`
---
# Constraints and concepts
-
**SFINAE trick:**
Introduce a substitution failure for types that are not supported
-
Can be done inside a
`decltype(...)`
trailing return-type:
## Example - Euclidean distance
```
c++
template
<
typename
Point
>
auto
distance
(
Point
const
&
a
,
Point
const
&
b
)
->
decltype
((
std
::
size_t
(
a
.
size
()),
double
(
a
[
0
])))
// comma separated list of expression
{
// last expression determines type
double
result
=
0.0
;
for
(
std
::
size_t
i
=
0
;
i
<
a
.
size
();
++
i
)
result
+=
(
a
[
i
]
-
b
[
i
])
*
(
a
[
i
]
-
b
[
i
]);
return
std
::
sqrt
(
result
);
}
```
---
# Constraints and concepts
-
**SFINAE trick:**
Introduce a substitution failure for types that are not supported
-
Can be done inside a
`decltype(...)`
trailing return-type:
## Example - Euclidean distance
```
error: no matching function for call to ‘distance(double, double)’
23 | distance(1.0,2.0);
| ^
specialization.cc:5:6: note: candidate: ‘template<class Point> decltype (((std::size_t)(a.size()), (double)(a[0]))) distance(const Point&, const Point&)’
5 | auto distance (Point const& a, Point const& b) -> decltype((std::size_t(a.size()), double(a[0]))) {
| ^~~~~~~~
specialization.cc:5:6: note: template argument deduction/substitution failed:
specialization.cc: In substitution of ‘template<class Point> decltype (((std::size_t)(a.size()), (double)(a[0]))) distance(const Point&, const Point&) [with Point = double]’:
specialization.cc:23:19: required from here
specialization.cc:5:75: error: request for member ‘size’ in ‘a’, which is of non-class type ‘const double’
5 | auto distance (Point const& a, Point const& b) -> decltype((std::size_t(a.size()), double(a[0]))) {
| ~~^~~~
specialization.cc:5:92: error: subscripted value is neither array nor pointer
5 | auto distance (Point const& a, Point const& b) -> decltype((std::size_t(a.size()), double(a[0]))) {
| ~^
```
---
# Constraints and concepts
## Definitions
-
Class templates, function templates, and non-template functions (typically members of class templates)
may be associated with a
**constraint**
, which specifies the requirements on template arguments,
which can be used to select the most appropriate function overloads and template specializations.
-
A
**concept**
is a named set of requirements consisting of valid expressions, associated types, invariants, and complexity
guarantees.
-
Each concept is a predicate, evaluated at compile time, and becomes a part of the interface of a
template where it is used as a constraint.
-
A type that satisfies the requirements is said to
**model**
the concept, or be a model of the concept.
-
A concept can extend the requirements of another concept, which is called
**refinement**
.
---
# Constraints and concepts
## Valid Expressions
C++ expressions which must compile successfully for the objects involved in the expression to be
considered models of the concept, e.g.,
.font-md[
<table width="100%" border="1">
<thead>
<tr><th>Name</th><th>Expression</th><th>Pre-Cond. </th><th>Semantics</th><th>Post-cond.</th></tr>
</thead>
<tbody>
<tr>
<td>Size</td>
<td><code class="remark-inline-code">a.size()</code></td>
<td></td>
<td>Returns the size of the container, that is, its number of elements.</td>
<td><code class="remark-inline-code">a.size() >= 0 && a.size() <= MAX SIZE</td>
</tr>
<tr>
<td>Element access</td>
<td><code class="remark-inline-code">a[n]</code></td>
<td><code class="remark-inline-code">0 <= n < a.size()</code></td>
<td>Returns the nth element from the beginning of the container.</td>
<td></td>
</tr>
</tbody>
</table>
]
---
# Constraints and concepts
## Associated Types
Types that are related to the modelling type in that they participate in one or more of
the valid expressions. Typically associated types can be accessed either through
`using`
or
`typedef`
nested
within a class definition for the modelling type, or they are accessed through a
*traits class*
, e.g.,
.font-md[
<table
width=
"100%"
border=
"1"
>
<thead>
<tr><th>
Name
</th><th>
Expression
</th><th>
Semantics
</th></tr>
</thead>
<tbody>
<tr>
<td>
Value type
</td>
<td><code
class=
"remark-inline-code"
>
X::value_type
</code></td>
<td>
The type of the object stored in a container. The value type must be
<i>
Assignable
</i>
, but need not be
<i>
DefaultConstructible
</i>
.
</td>
</tr>
</tbody>
</table>
]
---
# Constraints and concepts
## Invariants
Run-time characteristics of the objects that must always be
true
, that is, the functions involving
the objects must preserve these characteristics. The invariants often take the form of pre-conditions and
post-conditions, e.g. a class that implements a monoid operation must satisfy
:
.font-md[
<table width="100%" border="1">
<thead>
<tr><th>Name</th><th>Semantics</th></tr>
</thead>
<tbody>
<tr>
<td>Associativity</td>
<td>For any <code class="remark-inline-code">x</code>, <code class="remark-inline-code">y</code>, and
<code class="remark-inline-code">z</code>, <code class="remark-inline-code">f(x, f(y, z))</code> and
<code class="remark-inline-code">f(f(x, y), z)</code> return the same value.</td>
</tr>
</tbody>
</table>
]
---
# Constraints and concepts
## Complexity Guarantees
Maximum limits on how long the execution of one of the valid expressions will take,
or how much of various resources its computation will use, e.g.,
.font-md[
<table
width=
"100%"
border=
"1"
>
<thead>
<tr><th>
Semantics
</th></tr>
</thead>
<tbody>
<tr>
<td>
The run-time complexity of element access is amortized constant time.
</td>
</tr>
</tbody>
</table>
]
---
# Constraints and concepts
## Concepts
A concept is a named set of requirements. The definition of a concept must appear at namespace scope.
The definition of a concept has the form
```
c++
template
<
template_parameter_list
>
concept
concept_name
=
constraint_expression
;
```
---
# Constraints and concepts
## Constraints
A constraint is a sequence of logical operations and operands that specifies requirements on template
arguments. They can appear within
*requires-expressions*
and directly as bodies of concepts.
There are three types of constraints:
1.
conjunctions (combination of constraints using
`&&`
)
2.
disjunctions (combination of constraints using
`||`
)
3.
atomic constraints
---
# Constraints and concepts
## Requires clauses
The keyword
`requires`
is used to introduce a
**requires-clause**
, which specifies constraints on
template arguments or on a function declaration.
```
c++
template
<
typename
T
>
void
f
(
T
&&
)
requires
Concept
<
T
>
;
// can appear as the last element of a function declarator
template
<
typename
T
>
requires
Addable
<
T
>
// or right after a template parameter list
T
add
(
T
a
,
T
b
)
{
return
a
+
b
;
}
```
In this case, the keyword
`requires`
must be followed by some constant expression (so it's possible
to write
`requires true`
), but the intent is that a named concept (as in the example above) or a
conjunction/disjunction of named concepts or a
*requires-expression*
is used.
---
# Constraints and concepts
## Requires expressions
The keyword
`requires`
is also used to begin a
**requires-expression**
, which
is an expression of type
`bool`
that describes the constraints on some template arguments. Such an
expression is
`true`
if the constraints are satisfied, and
`false`
otherwise:
```
c++
template
<
typename
T
>
concept
Addable
=
requires
(
T
x
)
{
x
+
x
;
};
// requires-expression
template
<
typename
T
>
requires
Addable
<
T
>
// requires-clause, not requires-expression
T
add
(
T
a
,
T
b
)
{
return
a
+
b
;
}
template
<
typename
T
>
requires
requires
(
T
x
)
{
x
+
x
;
}
// ad-hoc constraint, note keyword used twice
T
add
(
T
a
,
T
b
)
{
return
a
+
b
;
}
```
---
# Constraints and concepts
## Requires expressions
The syntax of requires-expression is as follows:
```
c++
requires
(
parameter_list
(
optional
)
)
{
requirement_seq
};
```
-
`parameter_list`
a comma-separated list of parameters like in a function declaration. These
parameters are only used to assist in specifying requirements. (optional)
-
`requirement_seq`
a sequence of requirements, described below (each requirement ends with a semicolon).
--
Each requirement in the
`requirements_seq`
is one of the following:
-
simple requirement (arbitrary expression statement)
-
type requirements (the keyword
`typename`
followed by a type name)
-
nested requirements (the keyword
`requires`
followed by a
*constraint_expression*
)
-
compound requirements (syntax:
`{ expression } -> type_constraint`
)
---
# Constraints and concepts
## Requires expressions - Examples
### The array-like Concept
```
c++
template
<
class
A
>
concept
Array
=
requires
(
A
a
,
typename
A
::
size_type
i
)
{
{
a
.
size
()
}
->
std
::
integral
;
// compound requirement
{
a
[
i
]
}
->
std
::
convertible_to
<
typename
A
::
value_type
>
;
// implicit type req.
};
```
### An Arithmetic Concept
```
c++
template
<
class
T
>
concept
Arithmetic
=
requires
(
T
a
,
T
b
)
{
a
+
b
;
// simple requirement
a
-
b
;
-
a
;
requires
std
::
totally_ordered
<
T
>
;
// nested requirement
};
```
Additional invariants:
`(a + b) + c == a + (b + c)`
---
# Constraints and concepts
## Function overloading based on ordering of constraints
-
If concept
`A`
refines concept
`B`
, then
`A`
is more specialized
-
In an overload resolution, the most specialized (and most constraint) function that fulfils
the requirements is selected
```
c++
template
<
Array
Point
>
// constraint: `Point` must be model of `Array`
auto
distance
(
Point
const
&
a
,
Point
const
&
b
)
{
// (a)
typename
Point
::
value_type
result
=
0.0
;
for
(
typename
Point
::
size_type
i
=
0
;
i
<
a
.
size
();
++
i
)
result
+=
(
a
[
i
]
-
b
[
i
])
*
(
a
[
i
]
-
b
[
i
]);
return
std
::
sqrt
(
result
);
}
template
<
class
T
>
requires
Arithmetic
<
T
>
// requires-clause
T
distance
(
T
const
&
a
,
T
const
&
b
)
{
// (b)
return
std
::
abs
(
a
-
b
);
}
```
---
# Constraints and concepts
## Function overloading based on ordering of constraints
-
If concept
`A`
refines concept
`B`
, then
`A`
is more specialized
-
In an overload resolution, the most specialized (and most constraint) function that fulfils
the requirements is selected
```
c++
template
<
Arithmetic
T
>
requires
std
::
unsigned_integral
<
T
>
// concept refinement
T
distance
(
T
const
&
a
,
T
const
&
b
)
{
// (c)
return
a
>
b
?
a
-
b
:
b
-
a
;
}
int
main
()
{
using
Point
=
std
::
array
<
double
,
2
>
;
Point
a
{
1.0
,
2.0
},
b
{
2.0
,
3.0
};
auto
d1
=
distance
(
a
,
b
);
// (a)
auto
d2
=
distance
(
1.0
,
2.0
);
// (b)
auto
d3
=
distance
(
1.0
f
,
2.0
f
);
// (b)
auto
d4
=
distance
(
1u
,
2u
);
// (c)
}
```
\ No newline at end of file
presentation/_includes/template-specialization.md
0 → 100644
View file @
c042b840
---
class
:
center, middle
# Template Specialization
---
# Template Specialization
-
Template introduce a way of programming that allows formulating algorithms independent of the
concrete representation of data types
-
Often (hidden) requirements are given for the template parameters
-
Some types needs special treatment
## Example
Euclidean distance
```
c++
template
<
typename
Point
>
double
distance
(
Point
const
&
a
,
Point
const
&
b
)
{
// primary template
double
result
=
0.0
;
for
(
std
::
size_t
i
=
0
;
i
<
a
.
size
();
++
i
)
result
+=
(
a
[
i
]
-
b
[
i
])
*
(
a
[
i
]
-
b
[
i
]);
return
std
::
sqrt
(
result
);
}
```
---
# Template Specialization
-
Template introduce a way of programming that allows formulating algorithms independent of the
concrete representation of data types
-
Often (hidden) requirements are given for the template parameters
-
Some types needs special treatment
## Example
Euclidean distance of 1d points as floating-point numbers
```
c++
template
<
>
// Specialization for floating-point type double
double
distance
<
double
>
(
double
const
&
a
,
double
const
&
b
)
{
return
std
::
abs
(
a
-
b
);
}
```
---
# Template Specialization
## Primary Template
> .h3[Definition:] The first complete (non-specialized) template definition is called the *primary
template
*
. It has a special meaning in the context of overload resolution.
## Specialization
-
Always start with the keyword
`template <...>`
-
Specialized type(s) in angular brackets directly after the function name or class name
```
c++
template
<
>
return_type
function_name
<
Type1
,
Type2
,...
>
(
Arg1
arg1
,
Arg2
arg2
...);
template
<
typename
U1
,
typename
U2
,...>
// further template parameters allowed
class
class_name
<
Type1
,
Type2
,...
>
;
// Type1, Type2... might depend on U1, U2...
```
---
# Template Specialization
-
The
**primary template**
specifies, that a function or class is a template. It is not specialized,
but allows any template parameter. It is required to be declared (not necessarily defined)
before any specialization.
-
If no template parameters remain in the template parameter list, the specialization is called
**full specialization**
, otherwise
**partial specialization**
.
> .h3[Attention:] Partial template specialization is allowed *for class templates only*!
---
# Template Specialization
## Examples
```
c++
// primary template
template
<
class
T
,
class
S
>
class
MyPoint
;
// Type T... element type, Type S... index type
// specialization for element type `double` and index type `int`
template
<
>
class
MyPoint
<
double
,
int
>
{
...
};
// (a)
// specialization for any element type but index type `long`
template
<
class
T
>
class
MyPoint
<
T
,
long
>
{
...
};
// (b)
// specialization for element type `MyPoint<T,int>' where `T` could be any type
// and fixed index type `int`
template
<
class
T
>
class
MyPoint
<
MyPoint
<
T
,
int
>
,
int
>
{
...
};
// (c)
```
---
# Template Specialization
## Partial Template Specialization
-
The specialized types depend on remaining template parameters
-
Either the specialized type is equal to a template parameter or some qualification or might be
part of another template
```
c++
template
<
class
ValueType
>
struct
Rational
{
...
};
// specialization for all matrices of type Matrix<T> where T is a free parameter
template
<
class
T
>
struct
Rational
<
Matrix
<
T
>>
{
...
};
```
---
# Template Specialization
## Partial Template Specialization
-
Not allowed for function templates
```
c++
// Declaration of the primary template
template
<
typename
T
,
typename
Index
>
void
foo
();
template
<
typename
T
>
void
foo
<
T
,
int
>
()
{
...
};
// ERROR: Partial template specialization of function templates
```
--
-
Need workaround with
*tag dispatching*
/additional function parameters...
```
c++
// helper class template without any members and any data
template
<
typename
T
>
struct
id
{};
```
Then `id<int>` and `id<T>` for any other `T` are distinct types
---
# Template Specialization
## Partial Template Specialization
-
Not allowed for function templates
```
c++
// dispatch function. Function parameter depending on the template parameters
template
<
typename
T
,
typename
Index
>
void
foo_impl
(
id
<
T
>
,
id
<
Index
>
)
{
...
}
// (1)
// dispatch function for fixed `index` parameter'
template
<
typename
T
>
void
foo_impl
(
id
<
T
>
,
id
<
int
>
)
{
...
}
// (2)
// primary function template. Redirects to dispatch function
template
<
typename
T
,
typename
Index
>
void
foo
()
{
foo_impl
(
id
<
T
>
{},
id
<
Index
>
{});
}
```
-
In the code above, the dispatch function (2) is more constrained / more specialized than the general
dispatch function (1).
-
Both dispatch functions are primary templates here. No template specialization, but
overloading for different template signatures.
---
class: center, middle
# Quiz
---
# Template Spacialization
## 1. Which function is called in `main()`?
```
c++
template
<
class
T
>
void
foo
(
T
);
/* (a) */
template
<
class
T
>
void
foo
(
T
*
);
/* (b) */
template
<
>
void
foo
<
int
>
(
int
*
);
/* (c) */
int
main
()
{
int
*
p
;
foo
(
p
);
}
```
---
# Template Spacialization
## 2. Which function is called in `main()`?
```
c++
template
<
class
T
>
void
foo
(
T
);
/* (a) */
template
<
class
T
>
void
foo
(
T
*
);
/* (b) */
template
<
>
void
foo
<
int
*>
(
int
*
);
/* (c) */
int
main
()
{
int
*
p
;
foo
(
p
);
}
```
---
# Template Spacialization
## 3. Which function is called in `main()`?
```
c++
template
<
class
T
>
void
foo
(
T
);
/* (a) */
template
<
class
T
>
void
foo
(
T
*
);
/* (b) */
void
foo
(
int
*
);
/* (c) */
int
main
()
{
int
*
p
;
foo
(
p
);
}
```
---
# Template Spacialization
## Why Not Specialize Function Templates?
-
Function template specialization makes the overload resolution complicated
-
Might even depend on the order of declaration
> .h3[Guideline:] Do not mix template specialization and function overloading. Prefer function
> overloading in general.
---
# Template Spacialization
## Basic rules for overload resolution [[details](https://en.cppreference.com/w/cpp/language/overload_resolution)]
1) All visible names are collected (involving
*argument dependent lookup*
(ADL) in the
namespaces of the arguments)
2) All non-viable functions are erased from that list
-
Number of parameters must match (involving default function parameters)
-
There must be a sequence of implicit conversions to transform the passed arguments
into the function parameter types.
-
For function templates all template parameters must be deducible (
*
argument type
deduction
*
– ATD)
-
If the replacement of a template parameter by the type derived from ATD would
lead to a direct error, this raises a
*substitution failure*
. Candidates involving a
substitution failure are simply ignored (a
*substitution failure is not an error*
- SFINAE)
---
# Template Spacialization
## Basic rules for overload resolution [[details](https://en.cppreference.com/w/cpp/language/overload_resolution)]
3) A non-template function that is an exact match is the best fitting candidate.
4) For all primary template the most specialized one is selected.
5) If there are template specializations of the best fitting primary template, the most
specialized (matching) one is selected.
--
## What does it mean to be the most specialized template?
For two function templates that one is more specialized whose arguments can be inserted into the
other function but not vice versa, e. g.,
```
c++
template
<
class
S
>
void
foo
(
S
);
// (1)
template
<
class
T
>
void
foo
(
T
*
);
// (2)
```
Every pointer
`T*`
is also an arbitrary (non-constrained) type
`S`
, but not all types
`S`
are pointer
types. Thus, (2) is more specialized than (1).
---
# Template Spacialization
## For functions prefer overloading!
### Example
```
c++
template
<
typename
Point
>
double
distance
(
Point
const
&
a
,
Point
const
&
b
)
{
// (a)
double
result
=
0.0
;
for
(
std
::
size_t
i
=
0
;
i
<
a
.
size
();
++
i
)
result
+=
(
a
[
i
]
-
b
[
i
])
*
(
a
[
i
]
-
b
[
i
]);