Back to Basics: Function and Class Templates - Dan Saks - CppCon 2019

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi welcome there's the subject and that's me dance acts and let's just do it the with these back to basics talks the dilemma is in where do you start you know what is the baseline and what assumptions are you're allowed to make and so I prefer not to make too many assumptions and the consequence is that the early part of the talk but I'm going to do is fly by a lot of basic stuff which I think most of you know just to make sure I didn't skip any of the foundational stuff and then but what I would ask is that I don't want to lose anybody early on so if you see something you want me to slow down just yell whoa or something like that nice and loud and I will back up a slide and the other thing is it's very inefficient there is a mic there and we want everybody to hear the question but if you yell it out I will just repeat it back and that may be more efficient than yours stepping over people to get to the mic okay templates are a really big topic it gets easily spends a couple of days on templates so what can we do productively in an hour what I'm going to try to do here is focus on two big things what is what are the major features and some design rationale because there's a lot of C++ which seems very arcane but once you know the reasoning that led to these kinds of decisions it makes a lot more sense and so what I'm going to do is dip down into the detail just enough to clarify the big-picture stuff and to alert you to some potential problem areas and to point the way toward future study so I'm going to start with a real basic example a function swap that will take the value that's in I and put it in J and take what was in J and put it in I this is something that you want to do for an awful lot of different data types so it's a very good candidate for function overloading and so there you have a couple of overloaded functions one will swap a pair of intz the other ones swap a pair of strings and the way a overload resolution works is that it has a best matching algorithm where it looks at the type of the arguments being passed in and says Oh swap IJ I and J are intz which one of those swaps will swap instant it picks the right if you swap strings similar process happens picks a different function and if you give overload resolution something that doesn't make sense like trying to swap a string with an integer in this case it will reject it now in reality up operator I'm sorry function overloading doesn't require an exact match of the operand type to the parameter types that it will try some conversions but not in this case because the parameter types are references and references are fairly unforgiving about what they will bind to now when you look at the function definitions again most of us could crank something like this out pretty quickly on our own there's the version for int and there it is for swap you produce it by copying pasting and editing and after you've done this a couple of times you say okay I get the idea there's got to be a better way that for a straightforward function like this your odds of making a severe editing mistake are pretty small but the more the larger the function the greater your chance of making a mistake so we've like an automated way to do it and that's where oh by the way for those of you who notice what happened to the move operations I just say chill you know this is a back-to-basics talk there were plenty of talks on move semantics I don't want to cloud the picture with that discussion this is still functionally correct and there are plenty of other talks that move semantics so what function overloading does is it presents a nicer interface to the user you don't have to know the name of a different name for each different swap and so there's an ease of use there the problem is that it doesn't spare you the effort of cranking out nearly identical functions one right after the other that's where the the tedium and the error-prone activities creep in and that's the primary motivation for function templates so the thing to bear in mind is a function template is not really a function it's a tool for cranking out functions and Oh more formally what it is is it's a single declaration which can be used to crank out declarations when you know what type you want to apply it to so here is the declaration for a template swap that will swap two things of some to be determined type T that's no it's known as a function template declaration now that is enough information for the compiler to be able to compile a call on swap but it's not enough information for the compiler to actually manufacture the function that does the swapping for that you need a function template definition so with with templates just like with a lot of other constructs in C++ there's this distinction between a declaration which just establishes it exists and a definition which says and here it really is and in the case of a function template C this is still not producing any code this is just compiled time information of a function or set of functions to be by the way in this context when you use the less than and the greater than they're known as angle brackets now part of what makes it a little bit tricky talking precisely about templates is in the case of a function template they were actually two parameter lists there's something known as the template parameter list and the function parameter lists the function parameter list is familiar to you you've seen these and lots of other functions it's the template parameter list that is the comparatively new thing so what does that type name T represent well T there is a placeholder for a type to be determined when you decide what it is that you want to swap and the substitution of that type takes place purely at compile time there's no runtime aspect to that it's all compiled time magic the function parameters in this case a and B are analogous to you know function parameters in any non template function it's what gets passed when you actually make a call but and we used to be able to say in a very straightforward way that whatever gets passed to a and B gets passed that run time but no we can't do it that's simply anymore because of context Bert and Const of al which will shift what used to be a runtime computation into being a compile time computation as well so depending upon the context if that swap function is being called in a Const expert context then it could be that that argument passing has happens at compile time also but if it's not a context or context then this would happen at runtime now the type name T the T and that thing is essentially like a type def name and like any other type name it has a scope and in this case the scope begins when you say type name T and it ends when you get to the close curly brace of the entire template declaration or definition in this case and by the way there is a standard swab it's an error file called algorithm and it's a member of namespace STD it's one of umpteen different algorithms that are in the standard library and by the way it does use move semantics properly but okay so once again a way of thinking about a function template is it's a recipe for generating functions and and the process is to a large extent very automatic and there's a name for this process it's we call this template instantiation you go to use the template and the compiler figures out automatically okay what has to be generated to support this thing in the running program and so this is actually a process just like we have parsing and cogeneration and linking template instantiation as part of the translation process but and what do we call the result of that process we call it an instantiated function which a lot of people abbreviate to just call it an instantiation now this is a little bit unfortunate this is a classic example of where you have different subcultures in the industry using the same word for different purposes because in in the object oriented design world and in some other programming languages they use the word instantiation as a way of talking about objects created from classes and you'll notice that for do a large extent C++ programmers don't talk about object creation using the word instantiation we try to reserve it for this process because this is confusing enough without lunging those two concepts together okay so how do you actually use one of these things well then look the simplest and most straightforward way to do it is you just take the template name and you decide what kind of argument do I want to substitute for that placeholder and you actually name the argument inside a set of angle brackets so the name in this case swap angle brackets it that's the name of an instantiated function that will trigger the magic chain of events that will produce a real function swap that will do the job of swapping I and J and in fact the way you can think of it is that at the moment in your code where you say swap int I jet the compiler says okay I'm going to instantiate a function and let's see if I get my pointer will work here yes oh I I you just say you create an object you define an object you you allocate an object you construct an object we use phrasing like that to talk about the birth of an object and we don't and I say that I just call it an object you know this class produced an object oh the question was if you don't use instantiation in the design sense what do you say and the answer is there there isn't really widespread uniform terminology in the C++ community for doing this we just sort of wing it okay um so you can think of it as if I'll guess yeah there it is right there that is a declaration for a function that spawned from the template and and it happens right then when you go ahead and use the function similarly if use later on you say swap string of s comma T the compiler says Oh swap is a template i plug string in for type T and I manufacture a function with that name and that parameter list and in fact if you build your program and produce you get the linker to produce a link map that shows where the generated code is if this is a non inline function you'll actually see something that looks an awful lot like that in the physical layout of your program yes the angle brackets you're referring to right here because that because this function swap string is different from the function swap it those are two different functions and they have to have a unique name if you'll just hold on III have more to say about the syntactic issues here and I think you'll you'll be a little happier after that okay but thank you for for chiming in yeah so everybody the question was what's that angle bracket doing there is it really necessary and in fact it is there a uniquely identifies the name but I'm going to get into the mechanics a little bit further along and so on you can use swap with another type argument like a pointer to a constant character and it will substitute it in the appropriate places and so on you get the idea there's the compile note I gave a talk earlier on Monday about Const and go watch that I happen to prefer east Const but you know they mean the same thing it's just I I didn't want to write two copies of it to placate both camps you know life is too short for that kind of stuff okay now another important insight is that the compiler doesn't instantiate duplicate copies unnecessarily so for example here if I call swap string to swap strings s and T that'll trigger an instantiation if I then call swap string but with two different strings there's no need to instantiate another function they're both going to call the same function but if I then try to swap two integers that's a different function that's passing arguments of a different type you need a different function so that'll trigger a different instantiation okay a template can have more than one type parameter in fact there's a function very similar to this in the standard library it's a searching algorithm that says find everything from the first iterator to the second iterator looking for a value of type T and and what this says is that the collection of things that you're searching through is identified by an iterator type a pointer like tight and the value that you are searching for may be related to the iterators but it can be a distinct type so there's no real bound on the number of template parameters that you can have other than your ability to deal with it mentally and once again when you will instantiate the fine function you would have to provide type arguments for each of the type parameters like that now the keyword class is allowed in this context as an alternate to tightening and it means the same thing in this place in C++ whether you say class or type name there the behavior is absolutely the same but you can't use type name and class interchangeably in other contexts like you can't declare a class widget using the keyword tightening now why is this well when C++ introduced templates way back in the early 90s the key word type name didn't exist and be honest rostra had I think a justifiable aversion to inventing new keywords so he tried to repurpose old keywords and so class seemed to be the one that made the most sense now later on in this lecture I'll explain a use for type name and after that that is distinct from this and after you see it you'll understand why it's there but the standards committee saw that and said you know type name is actually a better word in that place how about if we just let you choose type name instead of class and that's how it evolved to the way it is some people stylistically prefer class I I prefer type name in most settings now let's talk about class templates for a moment um class template is a generalization of an object type again just like a function template isn't a real function a class template isn't a real class it's a recipe for making classes and where where there's a lot of similarity in the interface and the functionality of those classes so let's just here's a simple example class for rational numbers a rational number is characterized by having a numerator and a denominator it's an arithmetic type and a good first order guess is to say why don't I make the type of the numerator signed long integers and so the class definition looks in part like that small sample of constructors those two that plus equals minus equals are placeholders for a full set of arithmetic operations and then down there in the private members you have the representation of the rational as a long numerator and a long denominator it's not hard to imagine though that other programmers might want to vary the precision of rational numbers and if you didn't have class templates you'd have to do a brute-force copy and paste to crank out a bunch of classes with very similar code and just different precision and again all that copying and pasting is tedious and error-prone so what we do is we make a template out of it in this case the template type parameter T represents the precision of the the numerator and denominator in the rational number now after I did this I thought you know if it would have been a good illustration maybe I should have made that type parameter named P instead of T or precision and but after many years of giving presentations and making last-minute changes like that I thought better of it because I will almost certainly introduce a type of so I left it at T now again the compiler will automatically instantiate the class definition as needed when you go to use it and that results in an instantiated class which you also sometimes just call an instantiation and the name of the class for example R at rational long is simply the class name rational followed by angle bracketed long or angle bracketed unsigned int whatever you want to substitute for the type parameter and you can use the name of a class template instantiation anywhere that you can use any other type so for example here I'm declaring an object RL and it's type is rational with long precision if I get tired of typing out rational angle brackets long or whatever I can type def it like this you can use either the old-style type def or the newer using alias and the type alias use the key we're using either of those will work and then after that I can declare an object RI of type I rat and the compiler knows that that's a variable of the instantiated template type container classes the things that make up the standard template library container is essentially any class that contains object of some other type things like array as a container or linked list as a container and these things are really good candidates for turning into class templates and sure enough the standard library does that there's in addition to lists vector set there's multi set there's map hash map you know quite quite a large collection of these all implemented as templates with a type parameter representing the element type in the container whoops that's interesting I didn't mean to do that now here's an example of I'm declaring an object ratios whose type is a list where the elements in the list are themselves a type instantiated from a template it's a list of rational with with int precision and note the space there between the two angle brackets the two greater than signs in C++ o3 it turned out that if you remove that space the compiler would miss understand what's going on because it would think that thing is a shift operator and it would not recognize it as a closing delimiter modern C++ fix that little annoyance by putting in a little lexical hack so that now you can remove this space and the compiler based on context will recognize oh that's two angle brackets now once again this is a class template definition this establishes that the type exists it shows you what the storage layout is through the data members that are there but notice that it declares but does not define its member functions just as is common in many other classes you can do this with a class template so that means what what do you do about defining the member functions well you can write them outside the class just like you can write member function definitions outside a non template in class you can do that here as well now so here's something important to note though that in a loose sense a member function of a class is itself a template in the sense that if I instantiate rational for type int and I want to do a plus equals on integers I have to instantiate that plus equals member function as well and so if you're going to to write the function template the member like in this case the constructor is appearing outside the class definition itself in a sense that's a template to the member of a template is it has to be instantiated as well so and remember that that type parameter up there that T goes out of scope right down there so if you start muttering about T here without having restated that T is a type parameter the compiler won't know what you're talking about so you have to repeat the template type name T in front of the definition of a member notice that the the thing before the colon colon is rational angle bracket T but the thing after the : : is just rational let's dwell on that for just a moment is that the way it is in C++ is that when you're inside the scope of a class definition for the most part you can use the word rational the the identifier rational or rational angle bracket T interchangeably one or the other and they mean the same thing when you're in the scope of the class definition and only in the scope of the class definition so for example if you write the class definition this way notice the absence of the angle bracketed T's in the class definition you can also write it like that and these are equivalent and and you can be capricious about it but I would not encourage that to randomly put it in or leave it out the compiler won't complain it's better styled to do one or the other but as with the member declaration I should say definitions of non templated classes when you write the definition of a function like this operator + right here outside of the class definition you have to precede it by the class name and it's when you pass that colon colon right there that you re-enter the scope of the class so that the return type here and the class name itself have to have the angle bracket in T you can't leave it off there but once you pass the colon colon the angle bracket and T right here is optional so you could write it like that but to make matters more complicated when you're doing constructors and destructors the rule is you're not allowed to put the angle bracket in T after the name of the constructor or the name of hello there's a little bit of a lag here seem to have lost I wonder if I look got to pause here for just a second for technical difficulties looks like I lost the connection and I don't have my machine here so deep can I have the oh it came back okay alright yep thank you so my recommendation is you don't want to have to memorize these special cases I just say that whenever you're inside the scope of the class just leave out the angle brackets that's the easiest rule to follow after after the constructor in the district when the name rationale appears right after the colon colon either because it's a constructor name or yeah you're gonna it might also be a problem in a conversion operator as well okay but you don't want to know this you know you want a simple rule which you can live by and the answer is just leave it out once you know that when you're inside the curly bracket of the class definition or once you get past the colon colon you're in the scope and rational means rational angle bracket T okay here's the way to simplify things is just write the memory function definitions right there inside the class definition and a lot of the syntactic complexity disappears they look just like member function definitions in non templated classes oh and by the way they they acquire the implicit inline property when you do that and a lot of people have gravitated to this approach simply because it eliminates a lot of overhead characters that you have to write with a repeated template type name T in order to get this stuff to glue together a class template can have static data members and when you do this each instantiation of that class gadget-gadget int gadget float gadget of char star each one is going to have its own counter but just like with the member function declarations that is not a definition for the counter that's just a declaration for the counter it doesn't actually allocate the storage for the counter by the way why is it that that thing is just a declaration think about this if we typically put that kind of stuff into headers if you take that put in a header and you include it in several separately compiled files and you compile each one if that counter is a definition you got to multiply define symbol the definition has to be somewhere else now in C++ 17 they introduced the keyword inline you can now have inline data as well as inline functions and that permits you to actually put the definition there and the compiler will actually figure out how to make one copy and throw away any possibility of duplicate instances but that's a C++ 17 feature and you always could have constant static data members defined in a class that predates modern C++ but if you're not dealing with static constant members and you're using a dialect prior to C++ 17 then you have no choice but to write the static data member definition outside the class using a definition in that style and notice that once again it has to be template type name T in front of the static data member definition because in the same sense a static data member of a class template is itself a template when you instantiate gadget event and you need the counter you have to have a separate instantiation for the gadget int colon colon counter you can have members that are types this is very common in the standard library for example the standard list class vector deck they all have a nested member called iterator and if you write the definition of the iterator member outside of the list class template definition once again you have to repeat the template type name T in front of it because remember the T keeps going out of scope when you get to the closing angle brackets when you want to get back into the scope you want to say you know here's a member of something that was inside that class definition you got to say template tightening T again and once again a lot of people simplify this by simply saying oh okay I'll just put the entire member class and the member type inside the template and I don't have to worry about the repeated template type name T and I don't have to worry about the colon colons life is simpler when you use the member iterator you have to fully qualify it you can't just say iterator because it's a member of some class in this case it's a member of a class template so you have to say something like list string colon colon iterator and this is the way people wrote things 20 years ago and it's examples like this that that were a main motivation for the repurposing of the keyword Auto as a type specifier in the introduction of range for to simplify those loops that's the subject beyond what we can talk about but the thing I wanted you to just retain is that you refer to the iterator member by outside the scope of the class you have to refer to it by its fully qualified name now I'm not going to say much about friends other than to point you to a toe last year at this conference I gave a talk called making new friends it turns out that friend functions in class templates are actually very intricate and I gave a full one-hour talk last year just on that subject so rather than try to squeeze a that topic in here I will just point you to that one and in fact it uses the same rational number class example to illustrate the point I think having sat through this you'll be able to get a fair amount of understanding about the complexities of friend functions in class templates from that talk now a template argument can be an expression instead of just a type so for example there is a class template in the standard library bit set which represents a set of n bits a sequence a fixed sized sequence of bits and when you instantiate the bit set class you don't you don't give a type as the argument you don't say bit set in tore bit set float or bit said char star you just say bit set 32 or 128 or how every many bits you want to be there in the definition of the class template when you write the template parameter list you don't say type name here instead you you say what you want the type of that argument to be you can say size T or you can say int in fact you can use a variety of types including integral enumeration types pointer types pointer to member types all of those can be used as what is called a non type parameter in in a template now we see these use less often than the type parameters but they are used and and they are valuable they allow you to build constant values into a template at compile time so for example you could incorporate a fixed size buffer into a class by making the size of that buffer a compile time nan type argument to a template now there's a little bit about the packaging here with nan templated classes and functions we usually divide things information up between headers and source files typically you put declarations in headers and you put the corresponding definitions in source files usually the problem is you put definitions into header files if you include them in more than one place you get multiply defined entities and that's not though the way we treat templates turns out with templates you don't put anything in a source file you'd pretty much put it in a header file you you put the class definition for something like the rationale class in a header file and you put all the member functions there as well whether you intend them to be inline functions or not it's all there in the header now the consequence of that is that that information will then be included in multiply in multiple separate compiled source files and the instantiation of the same thing could occur in multiple separately compiled units you go to link what happens well normally you'd think oh those are multiply defined symbols C++ linkers are now smart enough to see what's going on and essentially they they throw away all but one copy of the instantiation of a particular like swap int even if it's instantiated in two separate compilations you link the program together and it'll throw away the duplicates and keep one copy of swap and that the entire program will use okay I think this is dresses the question about the angle brackets it's that C++ often lets you omit the angle brackets from the function calls so you can abs here represents a an absolute value function absolute value is yet another function which makes sense for a wide variety of signed integer types and you are allowed to call the ABS function without providing the angle brackets and what happens is the compiler will look at what's inside the parentheses and use that to deduce what was missing in the angle brackets this is a process known as template argument deduction here's an example we have our absolute value function which will take the absolute value of some object of type T and you can compute the absolute value of an int down here and notice that no angle brackets provided and the compiler figures out what was missing based on what's here in the parens same thing down here F is of type float the compiler will deduce that what's missing is the float but the name of the function is still ABS float that's what's instantiated it's just you're allowed to leave it out when you actually write the call and what this does is it makes a function template look like an unbounded set of overloaded functions it's a rule in a sense for manufacturing overloaded functions on demand it is it true for complex types or just for the primitive ones I got some examples let me just show you how powerful this is so for example we have our swap template which I showed you earlier which will swap two things passed by reference to T and here we have swap I J well what are I and J they are integers now swap wants to pass arguments by reference to T what does T have to be in order for swap to be able to bind a reference to in it has to be an int it's not much of a leap and similarly you can just say swap st and it figures out what's missing is strength or you can even do this swap a P comma Q and it figures out what's missing is float star now once again if you pass in arguments like here I try to say swap an integer with a string well there is no type T where you can have a reference to a t bind to both an int and a string so in this case deduction fail and you get an error message the compiler says I can't figure out what's going on rethink what your one right here you could have a function template F that takes in this case an array of pointers to constant T's and here I have an object which is an array of pointers to constant characters and I try to call F of names and and the compiler is actually able to deduce that the car that everything else about this and this have the same structure what I'm doing is I'm substituting char for T internal to the compiler it actually doesn't match like that is this what you were wondering about yep and so it figures out that the missing T was a type character here's one more you can have a function sort that will sort a vector of type T and here I have an object which is a vector of strings and I say sort names and it deduces that the element type in the vector is string like that pretty potent stuff now there are situations where the deduction just can't happen for example the deduction will only happen on function arguments not on return type so here we have a function f which has an empty parameter list and a return type of T and it won't deduce from the context that oh you want me to return an int there that just that won't work you have to explicitly provide the angle bracket int in order for that to compile now here's just a few examples I know that prior to C++ 17 argument deduction only worked for function templates it didn't work for class templates here's an illustration I've a couple of objects r1 and r2 which are rationals with int precision if I say swap r1 and r2 what will it the deuce the type parameter to be again I screwed up a little here I guess I wear the the timing on this is wrong but the but for number one there that's a valid call on swap of r1 comma R 2 it will deduce that r1 and r2 are both of type rational int and that's the type being swapped and in number 2 there what I'm doing is I'm declaring an object r3 as a rational with integer precision and what I want to do is initialize it by copying r2 which is also a rational with integer precision here's the thing that you couldn't do until recently you couldn't say rational r4 and leave out the angle brackets on the type rational and say oh you want to initialize our 4 with r1 what's the type of r1 oh it's a rational within therefore it must mean that you want r4 to be also be a rational int c plus plus 11 c plus plus 14 wouldn't handle line 3 but c plus plus 17 will okay now let's talk about this alternate role for the keyword type name you've seen it being used just to in a parameter list to say this t is a type to be determined when you instantiate the template but here's the other use and for this i need to back up a little and explain a little bit more about how templates are processed by the compiler and it's this concept of it's known as two-phase translation when the compiler is compiling source code remember for the most part you take template definitions and you put them in header files and we typically include the header files early in the source program so that the template definition declarations and definitions will be visible when you start to use a thing down there so as the compiler is reading through its going to encounter a template definition like that before the first use of the template at that it doesn't know what T is so what can it do for the most part it's not going to generate any code from it it just basically scoops up all of that information in the template definition and puts it in the symbol table for later use it just makes us an entry in the symbol table for a function named foo with a template parameter T and a parameter list T of X and says and I'll get back to you later once I need to instantiate something so this is the first phase of translation but what happened was because the compiler didn't know very much about what T was it was very hard to compile the stuff that was in the curly brace body of the function or class template and so consequently people would find that the compiler didn't do much analysis and then when they later go to use the thing to instantiate it they'd be getting error messages for things like missing semicolons or unbalanced parentheses and and the reaction among users was you mean you couldn't tell me that up there and the compilers reaction initially was no I can't but the the Standards Committee said well maybe there's a way we can make it so that the compiler can do an awful lot of analysis on the definition of that template on the first reading even if we don't know what T is and so that's that's the the two phases that I'm talking about are that the first phase of translation is when the compiler just basically parses the template definition and squirrels in a way in the symbol table the second phase is when you actually go to use the template and plug in a type argument or an on type argument to actually instantiate something then it has the compiler has all the information it needs to actually instantiate something and fully compile it to a semantic analysis and code generation and and that second phase of translation happen at every instantiation every new combination of arguments being passed to the template will trigger this second phase of translation now here's what why this gets tricky though is let's take an example like the standard string class the standard string class has in it a member type called size type it also has a member constant called n pause it doesn't really matter for this discussion what they mean you can go look it up when you look up the string class but this is common in in class templates to have member types and member constants like this so now here's a problem a make-believe function called munch it it's a munch function takes something of type T where T is a template type parameter and the munch function though doesn't return to T it returns T's size type so there's a presumption there that whatever size type is it's a member of the class the abstract so this is a template that will only work if the type being used as the type argument is itself a class type with a member named size type so if you pass in a class that doesn't satisfy this it'll fail to compile similarly n pause there is being used in the first line of the function body and so again this is a template which will only be compiled if the type T whatever type is being substituted for T has members size type and end pause so that's a constraint on what types are acceptable as a type argument so here's the problem the compiler wants to validate that snippet of code and it says but I don't know what T is I don't know whether or not T actually has a size type and an end pause how can you expect me on that first reading to do very much analysis if I don't know that stuff and here's why it really matters consider this look at that line right there what is that think about that what what does that line mean well I'll give you suppose that that size type is a type and end pause as a type that what does it mean think about that anybody got an answer here's what it is it's a function declaration what is it declaring it's saying that I is the name of a function that takes a parameter of type T : : n pause and returns a pointer to a size type okay what happens though if size type is a type and n pause is not a type it's a constant an object or something else now what is that turns out it's an object definition this says that I is an object of type pointer to size type initialize those parentheses are not function parameter those are parentheses around an initializer initialized with the value in pause yeah that's actually typo it should have been it's of type t : colons size type star pointer - not just that what happens if size type and end pause aren't in it what happens if size type and end pause are not types both of them are not types then what is that turns out it's a multiply expression it's not a declaration at all and the left operand of the multiply is T colon colon size type and the right operand is I parens T : : n pause which could be a function call or it could be a function style cast C so this information about what's a type and what's not a type dramatically affects the interpretation of a line in the body of a template like that and so there's a term for names like size type and end pause which are preceded by T colon colon where T is a template type parameter we call this a dependent name it means the meaning of the name won't really be known until we know what T is we have to keep our options open and by the way names that are not dependent aren't independent names they are non dependent names and non dependent names have the same meaning in every template they are not dependent on the type parameter so here again is the problem compiler writers wanted to be able to satisfy users who wanted to be able to get error messages early even before we knew what type T was but the compiler comes back and says but how can I do something meaningful if I don't know which of those dependent names are types and which of them are not because whether they're types or not turns out to determine whether something is an expression or a declaration and so the solution to that problem was this the standard added a line that said a name used in a template declaration that is dependent on a template parameter is assumed not to name a type unless the name is qualified by the keyword tightening here's what you got to do you got to write it this way that you say type name T colon colon size type and what that tells the compiler is obviously T is a type it says so on the first line of the template the question is what's size type the word type name there says go ahead and assume that size type is a type go ahead and handle the first reading of the template as if you can be assured that size type is a type notice that there's no type name in front of T colon colon end pause we want the compiler to assume that end pause is not a tight and that the real use for the keyword type name in modern C++ okay now I actually have about seven minutes left I this is a stunt that I pull every so often where it's not really the end I have a few more slides that's my hedge against mistiming so here's some important stuff I'm gonna run over looking wearing by a couple minutes but I think that you'll find that this is worthwhile now I've been using the word instantiation it turns out there's another word specialization and this is a poorly understood word so I think it's important for you to to understand this the real meaning of specialization is simply when you take a template and you take a combination of type arguments and pair them together that's a specialization so yet swap T is a generalization of swap but swap int is a specialization that swaps integers swap float as a specialization that swaps floats it's when you plug in a type and turn it into the name of a real function that's a specialization and everything that I've shown you so far it's reasonable for you to conclude that every specialization leads to an instantiation and that's not true because I haven't told you the full story by the way the the construct swap angle brackets int is known as a template ID and that's the name of the of the instantiated template now everything that we've been doing so far relies on what's called automatic instantiation where you just go ahead and use the template and a miracle happens the compiler just manufactures everything that you need but it also means that you can't control where that generated code winds up and this automatic process is great for most situations implicit instantiation leads to automatically generated code but occasionally you have to say no I'd like to place that generated code here or there like if you're working with dll's or if you have issues with locality of reference where you want to make sure certain functions are in close proximity you might want to control this stuff and the way you do it is something called an explicit instantiation where you actually write here's an example of it you can say template and then you write the Declaration of the Swap string function that would have been instantiated and that will actually tell the compiler I want it to happen right here where I wrote it it's not automatic anymore you've switched on manual and you can do this for any function you can do this for classes too you can have the type deduction work here as well you can instead of saying template void swap string you can actually leave out the angle bracketed int or string whatever and based on these types the compiler will deduce what was missing from the angle brackets and that's a convenience but here's a problem which is if you manually instantiate something in one translation unit what's happening in the other translation units if they are automatically instantiation of the same template are you going to get conflicts and the answer is yes so what you have to do is actually turn it off in the other places and the way you turn it off is by using an extern template declaration in the other separately compiled units this is the price you pay for the manual control by the way this syntax is as modern C++ it's not it doesn't predate C++ 11 for function and class templates yes it works for both now here yes go back to the previous slide yes it needs to the question is if I turn off the instantiation here how am I going to get code to work and the answer is I got to link it with somebody that's explicitly instantiated okay remember that I'm turning off the automatic process and going full manual here there's I don't believe you can mix automatic and manual in the same program your fully automatic or fully manual okay what I want to show you here is that sometimes when you write a general template like this it works great for a whole bunch of types like this is compute the larger of two integers but this call on max here is going to instantiate the template here using int as the data type but what about something like this where I have two arrays of characters and I compute the max of those I think a lot of people might say well it doesn't it do a matching and pick whichever is alphabetically the greater then it turns out Nancy is alphabetically greater than Dan because the Nancy would occur later in a dictionary but it turns out in this particular call it will actually produce that Dan is greater because Dan is at the higher address it turns out it's like it's going to compare the pointers not the contents of the strings if you wanted to have a max that compared the contents of the strings what you can do is an explicit specialization and the syntax is to put template empty angle brackets in front of a declaration to do something like this if max is already known to be a template see the syntax only works if there's already an existing Max template you can then explicitly specialize the behavior of Max for a particular data type like a pointer to a constant character and that will sustain invoke Smacks if it's max for any type other than pointer to character it'll pick the general template but if it matches that type it'll pick the explicit specialization and I want to make a point here about terminology a lot of people will just call that a specialization not an explicit specialization but a specialization and and confusion ensues from that for reasons I'll show you in just a moment so anyway let's just scroll ahead here by the way once again the explicit angle bracket T is optional and by the way you use explicit specialization sparingly because it turns out you can get a better effect by just overloading a function named Max to compete with the template and this is often a simpler solution okay so here here's where I want to end up is we have these terminology which is explicit specialization is when you write a declaration of this form a template angle brackets declaration and what you're doing is you're saying I want to take the general template and tailor it to work for a particular type argument I don't want the general rule to apply I want something that looks like the same interface but it has a different behavior that's an explicit specialization but the concept in general of a specialization is its anytime you pair a template name with a type argument and so here is a picture that I will leave you with which is that see a specialization is just pairing the template with a particular type argument now if you allow that if you explicitly specialize the template then that specialization will be implemented by an explicit specialization like when I say max of four character strings I'm not going to instantiate a template from that specialization I'm just going to use the explicit specialization but if my specialization doesn't match an explicit specialization then it will appeal to the template to do an instantiation it's a subtle difference but it's substantive because here's here's what the bottom line is that when you refer to this thing as a specialization what you're saying is I don't care whether it manufactures code from the template it instantiates or it uses an explicit specialization that's immaterial but when you call that thing swap int and instantiation you're saying I know it wasn't explicitly specialized and when you call it an explicit specialization you're saying I know it wasn't instantiated see the mechanics here are important because they exploit they elaborate your understanding of how the code was generated okay and I'll just leave it at that there were a couple more slides but I'm really out of time so there this is where I dropped the mic if I could but it's [Applause]
Info
Channel: CppCon
Views: 37,760
Rating: 4.9472528 out of 5
Keywords: Dan Saks, CppCon 2019, Computer Science (Field), C++
Id: LMP_sxOaz6g
Channel Id: undefined
Length: 63min 12sec (3792 seconds)
Published: Tue Oct 15 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.