Added section about template aliases

git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@618 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
Frank B. Brokken 2012-01-13 10:54:31 +00:00
parent 8799ee27b7
commit 01e2ef8119
9 changed files with 142 additions and 165 deletions

View file

@ -14,9 +14,6 @@ lsect(SUBTLE)(Subtleties)
lsubsect(DOTTEMP)(::template, .template and ->template)
includefile(advancedtemplates/dottemplate)
sect(Template aliases (C++11, 4.7)) TODO
includefile(advancedtemplates/aliases)
sect(Template Meta Programming)
subsect(Values according to templates)
@ -60,6 +57,9 @@ includefile(advancedtemplates/templateparam)
subsect(Structure by Policy)
includefile(advancedtemplates/structure)
lsect(ALIASES)(Template aliases (C++11, 4.7))
includefile(advancedtemplates/aliases)
lsect(TRAIT)(Trait classes)
includefile(advancedtemplates/trait)

View file

@ -1,99 +1,64 @@
Typedefs are commonly defined using the tt(typedef) keyword. In addition,
C++11 allows the ti(using) keyword to associate a type and and identifier, but
different from ti(typedef) it does em(not) define a type. In practice
tt(typedef) and tt(using) can be used interchangeably.
In addition to function and class templates, C++11 also uses templates to
define an alias for a set of types. This is called a emi(template
alias). Template aliases can be specialized. The name of a template alias
spacialization is a type name.
define an alias for a set of types. This is called a
emi(template alias). Template aliases can be specialized. The name of a
template alias is a type name.
The name of a template alias is a template name.
Template aliases can be used as arguments to template template
parameters. This allows us to avoid the `unexpected default parameters' you
may encounter when using template template parameters. E.g., defining a
template specifying a tt(template <typename> class Container) is fine, but it
is impossible to specify a container like tt(vector) or tt(set) as template
template argument, as tt(vector) and tt(set) containers also define a second
template parameter, specifying their allocation policy.
A template argument for a template template parameter is either a class
template or it is a template alias (expressed as 'id expression'
Alias declarations cannot be (partially, explicitly) specialized. Template
aliases can be specialized.
Examples:
Template aliases are defined as tt(using) declarations, specifying an alias
for an existing (maybe partially or fully specialized) template type. In the
following example tt(Vector) is defined as an alias for tt(vector):
verb(
template <typename T> struct X {...};
template <typename Type>
using Vector = std::vector<Type>;
template <typename T>
using VecT = vector<T, Alloc<T>>;
Vector<int> vi; // same as std::vector<int>
std::vector<int> vi2(vi); // copy construction: OK
)
So, what's the point of doing this?
Looking at the tt(vector) container, we see that it defines two, rather
than one, template parameters, the second parameter being the allocation
policy tt(_Alloc), by default set to tt(std::allocator<_Tp>):
verb(
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector: ...
)
Now define a class template tt(Generic) defining a template template
parameter:
verb(
template <typename Type, template <typename> class Container>
class Generic: public Container<Type>
{
...
};
)
Most likely, tt(Generic) offers members made available by the container
that is actually used to create the tt(Generic) object, and adds to those some
members of it own. However, a simple container like tt(std::vector) cannot be
used, as tt(std::vector) doesn't match a tt(template <typename> class
Container>) parameter; it requires a tt(template <typename, typename> class
Container>) template template parameter.
Vec<int> vi; // same as vector<int, Alloc<int>> vi;
The tt(Vector) template alias, however, em(is) defined as a template
having one template type parameter, and it uses the vector's default
allocator. Consequently, passing a tt(Vector) to tt(Generic) works fine:
verb(
Generic<int, Vector> giv; // OK
Generic<int, std::vector> err; // won't compile: 2nd argument mismatch
)
With the aid of a small template alias it is also possible to use a
completely different kind of container, like a tt(map), with tt(Generic):
verb(
template <typename Type>
using MapString = std::map<Type, std::string>;
--------------------
template alias (formerly known as "template typedef")
How can we make a template that's "just like another template" but possibly with a couple of template arguments specified (bound)? Consider:
template<class T>
using Vec = std::vector<T,My_alloc<T>>; // standard vector using my allocator
Vec<int> fib = { 1, 2, 3, 5, 8, 13 }; // allocates elements using My_alloc
vector<int,My_alloc<int>> verbose = fib; // verbose and fib are of the same type
The keyword using is used to get a linear notation "name followed by what it refers to." We tried with the conventional and convoluted typedef solution, but never managed to get a complete and coherent solution until we settled on a less obscure syntax.
Specialization works (you can alias a set of specializations but you cannot specialize an alias) For example:
template<int>
struct int_exact_traits { // idea: int_exact_trait<N>::type is a type with exactly N bits
typedef int type;
};
template<>
struct int_exact_traits<8> {
typedef char type;
};
template<>
struct int_exact_traits<16> {
typedef char[2] type;
};
// ...
template<int N>
using int_exact = typename int_exact_traits<N>::type; // define alias for convenient notation
int_exact<8> a = 7; // int_exact<8> is an int with 8 bits
In addition to being important in connection with templates, type aliases can also be used as a different (and IMO better) syntax for ordinary type aliases:
typedef void (*PFD)(double); // C style
using PF = void (*)(double); // using plus C-style type
using P = [](double)->void; // using plus suffix return type
-------------------------------------------------
template <typename T>
using Dictionary = std::map< std::string, T >;
Dictionary<int> ints;
ints[ "one" ] = 1;
ints[ "two" ] = 2;
---------------------- the really useful case:
#include <vector>
template <typename Type, template <typename> class Container>
struct Wrapper: public Container<Type>
{};
template <typename Type>
using VT = std::vector<Type>;
int main()
{
Wrapper<int, VT> wi;
// Wrapper<int, std::vector> wi2; ERR!
}
Generic<int, MapString> gim; // uses map<int, string>
)

View file

@ -29,7 +29,10 @@ parameter). It has the following elements:
for containers). Programmers may not immediately realize that these
defaults exist and be confused when the compiler rejects such
templates when trying to pass them as template template parameters for
which these additional (default) parameters weren't specified.
which these additional (default) parameters weren't
specified. However, the C++11 standard offers a solution for this
problem in the form of em(template aliases), introduced in section
ref(ALIASES).
it() Following the bracketed list the keyword ti(class) em(must) be
specified. In this case, tt(typename)
hi(typename vs. class)
@ -105,8 +108,8 @@ used), but here it is mentioned explicitly, allowing us to pass our own
allocation policy to tt(Storage).
All required functionality is inherited from the tt(vector) base class, while
the policy is `factored into the equation' via the template template
parameter. Here's an example showing its use:
the policy is `factored into the equation' using a template template
parameter. Here's an example showing how this is done:
verb(
Storage<std::string, NewAlloc> storage;

View file

@ -83,9 +83,6 @@ includefile(classtemplates/tuples)
sect(Computing the return type of function objects (C++11))
includefile(classtemplates/returntype)
sect(Template typedefs and `using' declarations (C++11, 4.7)) TODO
includefile(classtemplates/templatetypedefs)
sect(Instantiating class templates)
includefile(classtemplates/instantiations)

View file

@ -106,7 +106,7 @@ inline std::string &StringPtr::iterator::operator*() const
//CMP
inline bool StringPtr::iterator::operator<(iterator const &other) const
{
return **d_current < **other.d_current;
return d_current < other.d_current;
}
//=
//OPADD

View file

@ -1,60 +0,0 @@
The tt(typedef) keyword is frequently used to define `shorthand' notations
for extensive type names. Consider the following example:
verb(
typedef
std::map<std::shared_ptr<KeyType>, std::vector<std::set<std::string>>>
MapKey_VectorStringSet;
MapKey_VectorStringSet mapObject;
)
In situations like this the actual type of the key or the data stored in
the map's vector might vary. Some may store tt(string) objects, other may
store tt(double) values.
A class may be defined based on the above tt(map) allowing the
specification of the final key and value types. Example:
verb(
template <typename DeepKey, typename DeepValue>
class DeepMap: public std::map<std::shared_ptr<DeepKey>,
std::vector<std::set<DeepValue>>>
{};
)
This increases the flexibility of the initially defined map as it is now
possible to write
verb(
DeepMap<KeyType, std::string> mapObject;
)
The C++11 standard adds to this the possibility to pre-specify either of
these template type parameters. To preset the key's type to tt(std::string)
leaving the data type open for later specification the following
emi(template using declaration) hi(using: template declaration) syntax is
available:
verb(
template<typename DeepValue>
using MapString_DeepValue = DeepMap<std::string, DeepValue>;
MapString_DeepValue<size_t> specialMap;
)
The tt(specialMap) now uses tt(shared_ptr<string>) for its key and
tt(vector<set<size_t>>) for its data type. A similar shorthand notation can
be used to pre-specify the value type leaving the key type open for later
specification.
In addition to the above template syntax the ti(using) keyword
can also be used to separate a typedef name from a complex type
definition. Consider constructing a typedef defining a pointer to a function
expecting and returning a tt(double). The traditional way to define such a
type is as follows:
verb(
typedef double (*PFD)(double);
)
In addition the C++11 standard offers the following alternative syntax:
verb(
using PFD = double (*)(double);
)

View file

@ -43,6 +43,9 @@ sect(More extensions to C)
lsubsect(AUTO)(Type inference using `auto' (C++11))
includefile(first/typeinference)
subsect(Defining types and 'using' declarations (C++11, 4.7))
includefile(first/using)
subsect(Range-based for-loops (C++11))
includefile(first/rangebased)

65
yo/first/using.yo Normal file
View file

@ -0,0 +1,65 @@
In bf(C++) tt(typedef) is commonly used to define shorthand notations for
complex types. Assume we want to define a shorthand for `a pointer to a
function expecting a double and an int, and returning an unsigned long long
int'. Such a function could be:
verb(
unsigned long long int compute(double, int);
)
A pointer to such a function has the following form:
verb(
unsigned long long int (*pf)(double, int);
)
If this kind of pointer is frequently used, consider defining it using
tt(typedef): simply put tt(typedef) in front of it and the pointer's name is
turned into the name of a type. It could be capitalized to let it stand out
more clearly as the name of a type:
verb(
typedef unsigned long long int (*PF)(double, int);
)
After having defined this type, it can be used to declare or define such
pointers:
verb(
PF pf = compute; // initialize the pointer to a function like
// 'compute'
void fun(PF pf); // fun expects a pointer to a function like
// 'compute'
)
However, including the pointer in the typedef might not be a very good
idea, as it masks the fact that tt(pf) is a pointer. After all, tt(PF pf)
looks more like `tt(int x)' than `tt(int *x)'. To document that tt(pf) is
in fact a pointer, slightly change the tt(typedef):
verb(
typedef unsigned long long int FUN(double, int);
FUN *pf = compute; // now pf clearly is a pointer.
)
The scope of typedefs is restricted to compilation units. Therefore,
typedefs are usually embedded in header files which are then included by
multiple source files in which the typedefs should be used.
In addition to tt(typedef) the C++11 standard offers the ti(using) keyword to
associate a type and and identifier. In practice tt(typedef) and tt(using) can
be used interchangeably. The tt(using) keyword arguably result in more
readable type definitions. Consider the following three (equivalent)
definitions:
itemization(
it() The traditional, bf(C) style definition of a type, embedding the type
name in the definition (turning a variable name into a type name):
verb(
typedef typedef unsigned long long int FUN(double, int);
)
it() Apply tt(using) to improve the visibility (for humans) of the type
name, by moving the type name to the front of the definition:
verb(
using FUN = unsigned long long int (double, int);
)
it() An alternative construction, using a late-specified return type
(cf. section ref(AUTO)):
verb(
using FUN = auto (double, int) -> unsigned long long int;
)
)

View file

@ -1,19 +1,23 @@
The keyword ti(typedef) is still allowed in bf(C++), but is not required
The keyword ti(typedef) is still used in bf(C++), but is not required
anymore when defining ti(union), ti(struct) or ti(enum) definitions.
This is illustrated in the following example:
verb(
struct somestruct
struct SomeStruct
{
int a;
double d;
char string[80];
};
)
When a tt(struct), tt(union) or other compound type is defined, the tag of
this type can be used as type name (this is tt(somestruct) in the above
When a tt(struct, union) or other compound type is defined, the tag of
this type can be used as type name (this is tt(SomeStruct) in the above
example):
verb(
somestruct what;
SomeStruct what;
what.d = 3.1415;
)