C++17 : The Biggest Traps - Nicolai Josuttis [C++ on Sea 2019]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

It takes 1 hour 20 minutes to list only the biggest ones

👍︎︎ 8 👤︎︎ u/Shnatsel 📅︎︎ Jun 15 2020 🗫︎ replies

C++: The biggest traps

FTFY

👍︎︎ 6 👤︎︎ u/LennyKappa 📅︎︎ Jun 14 2020 🗫︎ replies
Captions
so my name is Nicola Azotus I'm writing books and it's all my fault because I have been in standardisation of C++ since the first standard so 25 years ago and I want to talk to you about C++ 17 suppose for 17 is now 1 year old to some extent even more and we make more more experience about the problems we get yes there are problems and experiences challenges but the managers say and that's my talk about the next minutes some reason my presenter is not working so I have to walk back and forth ok as you probably who is programmed in C++ 17 already oh you want to all challenge me okay that's good so maybe you'll find you hear other problems maybe you'll see the problems I saw and so let's wrap it up and if you have something more for me please come to me and tell me just one statement before we go into details C++ is community-driven that means we have no chief architect that means there's no one consistent design by one consistent person which is not always the case if you have one chief architect at all but we have about 150 different contributors to C++ currently and they all make suggestions have different ideas have different contexts have different ways to program have different style different experience different age different platforms so that's our biggest problem we are to some extent victims of our own success because this number has raised significantly and it's in C++ 11 so at least something we did right and we meet three times a year next week in Hawaii to work there and then we come together and just people propose a change and then we accept or reject or we suggest to modify sometimes we are not aware of the consequences so often we are so just as an explanation in case you you're wondering why is this all maybe not worked out or looking different or not homogeneous yes that's the way it is if you stand up by a community instead of a chief designer and if you complain I come back to you and say it's all your fault because you haven't been there okay good just kidding hey do your last hand UK yes yeah good helps I'm German sorry we are the dry guys okay so when we just one additional hint when we standardize we are we are working different working groups we have people working on the core language we have people working on the on the library we have special groups like for concurrency or tooling or machine learning or whatsoever and then finally we vote on everything I would assume that nobody who woods on the last day understands what he wrote about about everything they would so we have to trust the other to some extent and we make mistakes good that's for the explanation and I have to start with the first topic the first topic is about initialization FEMA will give a talk today about initialization so I just make it brief because I just want to point out that I use my style of initialization so we have a mess in C++ and the message that we started with initialization the way C did then we introduced the way as if as bestest with parentheses then we introduced uniform initialization with curly braces and the question is what should we use so some extent brace initialization gut should have solved the problems of having there one ways to initialize well because we are backward comfortable we still raise the chaos and the question is what style should be used and you will see in my slides I'm now a total fan of uniform initialization and why the first thing it's it's it's it's better it detects box which are not there for example if you initialize an unsigned with a maybe negative value or with a we're too high then it works the way people read code so if we have a vector of two integer values you have two integer elements surprisingly to some people who come from old C++ then you have with Auto you have everything the way it works so Auto a1 is initialized by an in 42 we come to that in a moment in C++ 17 we are now also allowed to initialize enum values only with integral when we when we pass an integral value the only syntax that is okay is to use curly braces we can use it to initialize aggregates now even with inherited accredits and you can always make sure various there are values in the object and there are some corner cases like here where you have a hash object created with curly braces and then using it as a function with fur anthesis and you can see that difference which for example also helps here when you initialize the vector of in initializing by all the elements read from an input iterator so from the standard input without with parentheses this would declare a function instead of taking the elements from standard and to read all the elements so by default this works fine using for uniform direct initialization we still have a few flaws that will be fixed in c plus plus 20 and but these are very very rare corner cases but there's one thing my first rap which is if i declare something with auto and i use curly braces but in cool sign instead then we ran into problem and now the the font size of the slides will improve a little bit so if you by the way could somebody turn off this light because this goes to the slide and I think they are a lot better to read if you turn that off somebody here from the yeah okay good so so the problem we made a mistake and C++ 11 oh yeah that's right that's one so when we use auto end initialization curly braces to initialize an object this was we said this is initialized as an STD initializer list instead of an end everybody would read the third line as initializing an int and we fix that with a wither as a defect formally in C++ 17 but you will find this fix even in old C++ modes if you switch to the right compiler version so in Visual Studio 15 GCC 5 or clang 3 8 this is fixed even in C++ 11 mode and there we have fix saying that now if you take auto and curly braces this initializes and int and if you use multiple values this is an error now before this was an initializer list event there's something in our world that sometimes there's a problem that's compromises so that came the people said but if I write generic code my generic code sometimes needs an initializer list so how can I create it and the compromise was and this was one of the worst decisions we made with C++ 17 the compromise was if you use an equal sign this is still an initializer list what this means an equal sign in an initialization changes the type of the thing you initialize that's a very very very bad design I hope we will fix that sooner or later but yes this is first trap you should know so in any case if you initialize things now you might use it the old way the old way using aggregate initialization running over a loop of values initializing an iterator with begin or initializing the members of a class and you see here I don't use any curly braces at all my new style will change that to the following so that we use curly braces everywhere and the most interesting thing where I discussed this with people and teams is the iterate over a range of values so iterating from 0 to 32 with the third line do you feel comfortable with that line for those who do not use the old style we are a multi style language but there is an interesting experience we had in a company where we teach that now for almost one year to a team of more than 200 programmers we are getting used to it we feel comfortable using curly braces instead of an equal sign or parenthesis here so some resistance of the curly brace initialization might be just cost me because we are too old to experience and that's by the way sometimes also the problem of the Senate Committee there are only experts with many years of experience try to teach C++ to beginners and you see what problem this causes ok so far for the beginning let's look and go through the different features we have in C++ 17 and where they create a problem first of all we have now if and switch with the ability that you can pass an expression to initialize something before you make your check or your switch this is good if you see something like this you have some status to initialize and then you want to check against the status and use it in the next if expression you can write that now as follows so in the if statement like in a false statement before semicolon you can have an initialization at the beginning and the scope of this initialization ends with the end of the if which is the end of the F heads if available and here's another example that's a interesting example if you need a lock-up to do something you can write it that way now so let's initialize a lock rot so let's lock the mutex and why the loot a mutex this lock we check whether a vector is empty and if not we print the first element and the same is possible for a switch so what is the problem here the problem there are two problems actually people are running into now the first thing is they think about do we need a name for this lock guard here so do we need a name for the object that does a lock well you can make the name as in simple underscore some people prefer this style some people hate it but you might say oh why not skipping the name at all so just declaring in the initialization a lockout and then during the if using it the problem is a lifetime problem here because this initialization locks a temporary object but at the end of the initialization the lock is gone because the destructor of the element is called so if you do that you access your vector without having a lock on it so don't do that by the way this is also true if you do the same trick with the for loop this is nothing new but for loops with Lockhart in the initialization are pretty rare and by the way you no longer need here to pass the type of the mutex to do to class template argument deduction which we will discuss later here's another problem the funny thing story about this is this was a back report I think against visual C++ they said hey you knew if feature runs into a quorum so what did they do they had an acid if expression so I said first of all we have to call something that returns the pointer and then we use this pointer to some extent and because people like new tools and want to try them out they switch this code to this so they said let's initialize the object and then check it so what's missing here you see it all I'm pretty sure we have forgotten that in the first expression there were two if statements so two condition check so beside the initialization the first if was also a check whether the initialization returned the null pointer so there was missing an implicit if you nearly expression before we check the additional condition so people we were we were at least discussing a little bit time of this did we standardize something wrong no I don't think so but you have to be aware not just switching something that looks like an initialization into an if statement good aggregates before C++ 17 this class struck TV or this tract was not an aggregate so it did follow the roots of classes which means the objects of this class of this class of this type had an implicit default constructor and you could create them with this implicit before constructor so which mean which meant that you could create the object without curly braces and with empty curly braces and with curly braces you got the values initialized without not if they didn't have a default constructor like a string name you couldn't pass multiple values in a way that you want to initialize both the parent elements and the derived elements neither with nested curly braces nor without nested curly braces to do something like that you should you did have to provide a constructor taking the necessary arguments and pass them to the base class this has changed since C+ 4:17 this is by rule and aggregate an aggregate can now derive from another type the other type might not be an aggregate so there might be for example strings involved so now the initialization with curly braces you see on the bottom is now valid both with NASA initialization so with the inner curly braces you pass two elements to the base class and then defaults to your derived class to a member of the derived class and you can by the rules which were already there and see skip the inner braces and then they fill up as they find elements from top to bottom and from in to out what is the problem with this well look at this you have programmed this you have a data structure and in this data structure yes for whatever reason said I can't create this as a with a default constructor now before this was in there before this was an aggregate that meant this should not work to create something but with C++ 17 this is an aggregate now and see what you can do using a default constructor not passing any elements and no curly braces was an error and still is but using empty curly braces was not an error this did compile and when I tweeted this around I think somebody answered what this is a what of what the of c++ of the month so yes this did work and this was code that was valid we had it when we found that we immediately raised this issue for C++ 17 and this will be fixed in C++ 20 so in C++ 20 this will behave as both 19 comma dot nine nine nine percent of the program as well as that if you have a deleted constructor this will mean that you can't create objects by the ways we by the way we had some thoughts some yeah we did fight about this change there were people against this change so they thought it's a good feature that CD one is not possible but CD two is possible and you know why because we could force users of aggregates to pass something to initialize and if it's nothing so we could force here initialization but we discuss it and it will be fixed as we want to use it okay heap memory allocation you might have heard that heap memory allocation now supports alignment of data so since C++ 11 you can say my structure is aligned so that the address of object is and an address dividable by 32 without remainder and you can even in the Declaration say that Val 2 here is 64 bytes aligned so what's wrong or what's the problem here was C / 417 well this in C++ 11 was only supported on the stack not on the heap so on the heap this did not work we had to invent something and the biggest problem we needed was if we supply this feature on the heap we need new overloads of operator new and delete because you can implement operate a new and delete yourself and to some extent you need a way to pass this 64 to the implementer of operator new and delete and that was introduced in C++ 17 so we can now say on the heap so with new let's create an object of this type initialize or not and you know what this will how specific and a new operator new and delete and which will which you can implement yourself we have something like 20 different overloads of operator new now provided in the standard some of them are necessary some of them are derived from the others so what is the problem so why do I tell you that if we talk about traps of C++ C++ 17 I thought when I document this feature so oh wait a minute if when I document this feature so heap memory aligned allocation this will take me one day in the book it took me one week and the chapter has more than 30 pages because there are some flaws and some issues even explaining the situation before see before 17 and here's the biggest thing we have you can in honor stack say beside the fact that my objects in general are 32 by the lines let's in a special Christ case align them by 64 bytes which you say since C++ 11 the way you do that with Val - he they're just writing in front aligned as 64 so how do you do the same on the heap well you are using you're passing an argument to operate a new so you're passing to a to new and you align val chi type which is especially designed for that and that exactly what the new operator new takes as an argument and say what your favorite alignment is and for example here's 64 by the way if this 64 is your default alignment or less the normal operating you will be called just your operator new will be called if this 64 is higher than the default alignment of your type so what's the problem the question is how do I call delete because in heap memory pools you can now say if there's a specific alignment requested I use a different pool of data for my heap memory allocation so when I free it when I delete it I should free and delete it from the same different tool so I should not go the normal way and the point is that means when you call the need you have to pass this alignment request again because it's not stored in the type as you know the types only are pointers to memory so the delete needs an additional alignment argument to be perfectly portable but we have a problem we have no delete within syntax saying um here is the argument for your delete or in other words we have no placement delete we only have placement new in the language so what we really need it would be something like this to say when I called you need I pass you that the object was aligned to 64 byte to 64 byte that's not supported in the language so that means you have to call the delete operator directly not by calling just in it by saying I want to call the operation of the implemented operator delete which you see on the top so you call the global operator delete with p1 and then as a second argument you call the alignment ok that might not be very nice but maybe necessary but that's not all the problem is you might not know where the objects of this type have their own operator delete or not for Strings as requested here we know they have no specific operator delete defined so you know that you are safe if you call the global operator delete as he if you have your own type then you don't might not know whether this type it was has its own operator delete defined so there are two options either you call the global operator delete s on the last line or if it has its own implementation of operator delete you need two syntax on the line above which says I go to the types operator delete and call it so as a caller of the delete you have to know whether there's a specific operator delete for alignment design and that's a problem which for which we need placement delete we don't have it yet so you have to know more than necessary to deal with this feature okay good something totally different most operators have no Evelyn evaluation order in C++ that means if part of the operations are side-effects where you call something you don't know one of the things that when the things are called except for operators plus plus and excuse me so and so to M percent and or and the comma operator where we guarantee to go from left to right to evaluate the expressions from left to right so this was the example similar example was in be honest true soup c+ passbook and this example turned out to be invalid for years for centuries and so people thought that this example gives out prints out it worked it's sometimes work if I believe assuming that first we replace the first eight characters by nothing then we find the word even and replace that four characters by sometimes and so on the problem was this was not defined other outputs of this before C++ seventeen could be this because there was no guarantee when the find expressions were evaluated so it could be that we call s dot fine before we replace the first eight characters by nothing and then we would replace we would get a very different index where we replace four characters by some times so this was called that accidentally most of the time were correct but only accidentally and we have the same error at the same problem a couple of other places so here for example if you print out at the function the result of the function called F G and H people assume that F is called before G that's a wrong assumption it might be that G is called before F and if there are side effects this has an interesting effect and the other example is this print out incremented i and then decremented I is also it has it was not defined what was printed here in fact if you tried it out at different compilers possible outputs were 1 0 0 0 or 0 minus 1 we fix that so we fixed we have a new definition now for guaranteed evaluation order for some of the operands not for all so for the dark for the left and right shift operator as it did because it's used in function calls and also for function calls it's guaranteed now that the left side the left expression that which defines which function is called is evaluated before any argument I pass the order of the argument evaluation is still undefined so on the right side a B and C might be evaluated in different order but it's guaranteed that X / on the on the bottom here is evaluated before anything with a B and C if there's expressions so it's guaranteed now that the example on top now gives the first in C++ 17 that this is guaranteed to call F before G and this is guaranteed to print 1 0 since C++ 17 which might change your existing behavior in your programs who didn't know there's change only few why is this something you should look at you might have test Suites expecting some specific output and suddenly your output is different when you compile with C++ 17 here's an example we ran into in some project it's a simplified version of that here I have a function printing an element of a vector I pass the index and a prefix on optional prefix so I print first it perfect and then I call at because I double check whether the index is valid and this ad called might throw an exception so and then here's some code there call this function this code intentionally katharsis the error the exception because it iterates from 0 to size including size using the old style without curly braces by the way this is an old slide and the problem is yeah the using the index which is equal to the size of the collection is of course wrong and should create at cause as this exception and the interesting thing is in a couple of compilers this creates a following output look at this very carefully we print the elements the first four elements and then for the invalid index we run into the output of print a lamp the output statement and their first at is called before we print out the prefix so therefore we don't print the prefix yet so just at is called which raises an exception and we print out the exception because one other option of this is that first we print the prefix and then we make the add call so there were two differ options what could be printed here and couple of compilers did they do what the first what the first output demonstrates that that would no longer be valid so it is now guaranteed that the Preferences before we evaluate the at and your output and if you have a test feed or something like that might change accordingly okay that's just a simple case but there are more corner cases like that I don't think this makes invalid code suddenly valid but if you have test Suites like that and you expecting specific output the output might have changed here okay so far language features let's talk about templates we have a compile time if if cons extra the compile time is is useful if you have different checks and not all the checks are valid in a template function or a function template so look at this we want to convert something we pass to a string to a string and sometimes we have to call two strings sometimes we just get a string we return it back and sometimes we call the constructor of string and if you for example pass 42 so which is an INT calling the constructor which is the last elf statement here would be just invalid code the invalid code means that this will not compile and so with the run time axis should not work because with the run time X if it's with me with the run time if all the lines all the statements has to be valid so we switched to a compile time if and with this compile time if we can make sure that if you pass 42 and you want to convert it to s and the last statement here the last return statement is not compiled at all and must not be valid code okay so that's a good thing with compile time is two things here to command first of all if you are used just blindly that if return if return needs no else the do this here because if you do that here you underscore the last return statement so you learnt that that return is good enough because this must be a compile-time else otherwise this cold always have to be valid so it would no longer depend on whether the compile-time candidate conditions apply or not there's another problem that though here which is more severe or not surprising to some people so let's call a function foo and at compile time we decide is it is if this is an integral value and the value is greater than zero I call myself recursively again and a couple of people might assume now hmm let's wait I don't think we need the else case at all in our program so let's place an aesthetic assertion false let's play static assert false here and suddenly your code might no longer compile even if you never use the else case so even if you only call foo with integral types so what is the problem the problem behind this a sophisticated rule of template instantiation template in sense iation is done in two phases the first phase is let's check whether the code is valid without knowing how the template arguments are deduced or how they are specified so without knowing what the type of tiers the rule is without knowing what the type of T is everything that does not depend on T has to be valid and if you write static faults this does not depend on T so this will lead to the effect that if you you see this template definition your compiler stops compiling and says I have a compile time error even if foo is never called this is the effect of this static assertion the same is true if you have some undeclared function calls this check whether undeclared exists or not is done at definition time so when the template is seen there must be a function undeclared otherwise this template function template will not compile even if the else case has never used even if it's not called only when chords and expressions depend on template arguments they are delayed until instantiation time so when we know what the type of T is so beware of this static assert a couple of people run into this and we're very surprised about that so you're in the static assert you would have to say in this bright the same is integral check once again or the opposite not it is integral by the way if you're using visual C++ this will work so because witless C++ has not correctly implemented the two-phase handling of templates for those who will tomorrow at attend my my class fully a class about modern C versus template programming we will talk a little bit more about that they know that they have done that for years yet they can't easily switch now to the correct behavior so they have now a path to fix this but this will not be a problem in which with c++ so the assertion will not fail and calling undeclared will not say you will be might be a little bit surprised if you then part this code to GCC and see a problem there good something else class template argument deduction so we have now in C++ and you feature where we say complex number no templates class templates no longer necessary need a specification of all template parameters so you can say instead of space that my complex takes two inch so the class has to be a complex event you can let the compiler did use that from your constructors so it's enough to say with the old always the new syntax to say I pass to a complex to integral values and that's it because according to the constructor taking to int the compiler knows that for this class template deduction works and that the template parameter of this complex type is int which of course has to be there has there can't be any contradiction so if you pass an engineer float or floating-point values or double the compiler has no clue what to do and will not compile here which is especially useful for lock routes for example you can skip now passing to lock guards the mutex type because the mutex type is deduced from the constructor of the locker where you pass the type of a mutex implicitly here's another interesting example I recently learned so if you call sort now and you use one of the standard function objects the standard function objects like STD greater usually in the past and needed the element type with C++ 11 we fixed that no what no it was 14 I think that you only need empty angel brackets but even the empty angels brackets are no longer necessary because now according to the constructor the implicit template argument void is deduced here which works fine so we can write this code now that way whether this is more readable or not I'm not sure about that but it's possible now so that's a good that's a good moment where I want to see whether you are all awakened so who has used the class template argument deduction already in some code or at least in some sort experience so let's see whether you know everything right so this is vector vector class definition in on top you have all the constructors so we can say now my vector without specifying the element type takes in curly braces two elements 8 and 15 and then the compiler finds out which constructor is called so which constructor is called here the last one taken an initializer list because to do to some language routes this has the highest priority of those who might apply and this will initialize a vector of int so it will deduce because that the type T is int because this is an initializer list of int and that works and what what you always need is somewhere in the constructor a T so your type of template argument you want to deduce so if you have one element here this will also work now so you got it whenever wonderful let's talk about some other things what is the effect of this statement any suggestions eight element of value 15 that's correct why that because the initializer list constructor requires curly braces when you don't use curly braces this can't be passed to an initializer list constructor no way so only the others are possible and the only one that fits or the one that fits best is the one taking a size element size N and then the value of T good and if you pass just one element what constructor is called here so which how do we decide how do we did you see here the what default but if we call the default constructor how do we deduce type T well the answers this will not compile because we find now construct a matching constructor or the matching constructor we find is not able to deduce type of T so the constructor that probably will be called as a third one taking a size T element but it could also be just an element to initialize the vector of endor so the come in any case the compiler doesn't find a constructor taking T so what how about this eight empty string literals eight zeros well I would say if we take v3 eight times an integer of 15 this should be eight times a cons character pointer did you say pointer no what is the type of the two double quotes that's yes here it is it's an array of one character which contains a back set zero and by the way that's did used yes type T is deduced here as being an array of one constant character and the reason is that the constructor takes T by reference if it would take the deccan the argument by value the type would decay to a cons character pointer but because the constructor it's a fourth ones on top takes it by reference T does not decay though the type of T is connector one it does not compile because internally in the vector this type is used T is used in a couple of places and some of the places are not valid if this is a array okay are we done yet no more surprises STD said of string let's initialize a set of strings and let's call the vector with curly brace in passing erasers passing the beginning and the end of this vector of strings what is the effect which constructor is called the one with the initializer list all agree but yes she's right yes the rule is curly braces initializer list is valid this is the best fit we have so because we don't have a template here nan template it hits template initialization so yes this is an initializer list so this initializes a vector of set of string iterators okay which would not be the case if you explicitly if you wouldn't use class template argument deductions and would say vector of STD string then it would initialize this by the elements of the set so sometimes class template argument is actually is to some it surprises so what happens if we use parentheses instead then the initializer cut list constructor can't be it and it's an error why is it an error can't reduce the type yes the constructor called is a vector constructor taking two input iterators but we can't deduce type T from these input iterators okay there's no T in this constructor involved at all so for that reason in the standard we have what we call deduction guides we have a deduction guide helping the compiler here saying when you get a pair of iterators for initialization please did use T to be the value type of the iterators that's part of the standard so now still with curly braces we have the old behavior but with parentheses we did use a vector of string here and this will initialize where the vector v7 by all the elements in the set I would suggest that you use not class template argument deduction I would suggest that you explicitly specify the element type in this case are we done this is by the way the fully specified deduction guide are we done now well what happens if I pass in curly braces to string literals of different size which constructor is called the initializer list one the last one still yeah curly brace the same type so yeah initialize a list of T this initialize the list of T is passed by value now and now excuse me because we have two different types a they decay to type T to the two a common type which is called karakasa so this works this will initialize a vector having these two string literals so Khan's character star not STD string and what happens if I use parenthesis here we don't have such a constructor no this is not an error this will compile well I should say I should not say this is not an error but I should say this compile this is comprised which constructors taken the one with the two iterators yes because these the input iterator constructor takes the arguments by a value that means both string literals decay to Khan's character staff concur Kosta is a valid iterator so this has an interesting effect that we initialize the vector of character because we are pointing to characters and we initialize the vector with all the elements from the address of the high string literal to the address of the world string literal wherever they are located in the program at best at best you get a quorum okay so yeah we have some interesting effect be careful with class template argument deduction it works fine in many cases but not in all and we have seen these bugs already in production code or in s compiler feedback as for feedback to the compiler vendors so far let's talk about the library okay half an hour left we have new vocabulary types optional variant and any let me talk about one of them which is the most interesting well the most interest is probably very end because it introduces a new way of polymorphism but it works pretty robust and pretty well unless I don't know a problem I should know yet I don't know yet the problem I should know so let's talk about optional optional it is interesting it's just saying I have an optional object so that means if I have value semantics so my object always has a value I can say no there is no value and by declaring it as an optional string you might know also this type from boost so what's wrong or what's a problem with this datatype a couple of things are well something you should know first of all you can compare optionals directly to values of the underlying value type so that means you can say if my optional has an int I can compare this optional indirectly to 42 that's a feature that's not a back but it has leads to some surprises look at this I can't check whether an optional bool is less than false and this might be true because if there is no value the rule is any optional value that is has no values or any optional state that has no value is less than any other value which means we can put optionals in a stats or can solve them and at the beginning in front of the the minimum values are always the ones that that have no value no value is less than any value that's a rule for optional so that has this effect you can write this thing or you can also compare against an unsigned value and checking whether it you have a value less than an unsigned zero okay and here we have more funny stuff if you have an optional boo initialize with fault there's a difference between checking is there no optional value or is the optional value false yeah so beware by the way the reason we have optional in C++ 17 and not in C++ 14 is that we couldn't agree on how to deal with this situation and we thought about having some special specializations for optional bool but at the end we thought this is not worth it I mean people should know this trap and beside the fact that's an optional bool is something some interesting objects so you have a three-state object maybe you should use something different there but sometimes it makes sense of course and the same is true if you have an integer pointer a raw pointer which is optional there's a difference between applying the not operator which means is their value and checking against an eye pointer so these two codes are different which is especially interesting if you have these lines in generic code yeah so there's the difference between these two things and suddenly you pass an optional to your template and suddenly yeah one of them might work or might not work I'll do what was intended okay there's another problem with optional please look at this there's a function give me the value of the optional and this function is thus called dot if that's a memory function called value so if you have a function getstring returning an optional string though I might be string or might be not then you can call value and you get a copy of the contained value can assign this value to the underlying object value will throw an exception if there is no value so the code on top is safe there will be the value in the string so a will be a string if there is a value otherwise the exception will be thrown you should not if you don't know whether there's a where you just use the star which you can also use but the star will not double check whether there's a value so it requires that there's aware that you know that or that we have checked that the interesting things are the two lines on the bottom that now r1 and r2 because you might have the assumption that references extend the lifetime of return values which in principle is correct so in principle you can say whatever get is returned by this function a reference extends the lifetime of this value and an optional is a very atop however we have a problem here we are not using getstring we are using getstring value get string value means get spring gives you a temporary object and value gives you a reference to this object and then the rule of lifetime extension does no longer apply because you would extend the lifetime of the reference not the lifetime of the object this reference refers to so this is this would mean that r1 and r2 when you use them are referring to a deleted strength so don't do that and this by the way is a general problem we have in C++ we get more and more reference type with types with reference semantics and call the third friend semantics we will we will get more IRS like this you will see in a moment other examples here's one example where people might hit this directly indirectly without knowing it a vector of int is returned by function called get vector let's iterate over the interval use so let's do it like this oh it might happen that my vector of n return is not there I have no vector of n so I just switch this code to say let's call a function get optional vector with the return time is an optional vector and now let's iterate over the values and this should be safe because value will throw an exception if there is no value but the problem is this code will iterate over a deleted vector because internally in the range space for loop exactly an auto reference used like an r2 so internally the range based follow uses a reference and it will use this reference to a reference to the return type so that the return type and the return return object is gone so this is code that will compile but you are iterating over a deleted object beware of that that's by the way some reason that some when some companies say the range based fit for loop is dangerous it's it's a problem of a combination of range based followed and applying on right side something that gives the reference to a return value this is not only a problem with optional also if you return I don't know a temporary object and and like a vector and and you use the index operator there you have the same problem then we have string view this is string view this ring view is a new type it's not a very type it's more like a pointer type it's it's I say I would say it's a very dangerous pointer to a character sequence with all the nastiness of a row pointer just hidden in as a wolf in a string fashion so it's looking good but it's very deep evil so think about that if you string view it's great but it looks like you have a string but you are not only owning the characters below under underneath so for example there's no guarantee that there's a null character there's no guarantee that if you call data that you don't get the null pointer a couple of things are different but it's so nice to use you can you'd say well if I have a function taking a Const ring by reference and I pass a string little this has a problem that the string literal has to be converted to an SC D string first so this is a pretty expensive operation because we copy the values unless the short string optimization is used now if we switch instead to taking a string view we can even pass it by value because it's cheap and what we just created a string view as an object taking the original string literal and as a string literal exists throughout the whole program this code is safe so far a spring view using as a wrapper for a string literal as safe and be safe some computations like allocating memory and compute how long this string literal is etc which sometimes can be done at compile time so we also introduced there that we have it at conversions from string to string you and from string view to spring spring to spring view is cheap we just refer to the characters of a string and therefore an implicit conversion and you get something like this so you see that it's now you have a string you created from this string on the on the left and but beware of course why we have no lifetime problems if we refer to a string literal if we refer to a string we have to make sure that the string is still valid so the first thing is don't use a string view as a return type so here's an example let's concatenate two strings use let's add them convert it to a string and then return the string this will silently the return that the result of the plus expression here is an STD string but as we have an implicit conversion to string view this was silently converted to a string view without a warning without a warning in the compiler so you will not see that you view to on an object that is deleted already if you use this but there are more severe errors possible and we have seen that all ready here's my favorite example which I also used to teach the right programming of templates assume we have a straight view referring to a string literal which is safe as we just layout assume somebody has to implement it operator plus for string view the correct way not returning a string view returning a string and that's fine we have to string views so we have two views to a string we concatenate them so we created new string not of you a real string with data allocated to it and we return it as a string that's safe code that's a safe fun now let's assume in the same program somebody wrote a cool template the couch amplitudes let's compute the sum of two T's a function template and of course the result is a T so if I add two int I get an int if I get two I don't know other complex values I get a complex values so he wrote this template not thinking very carefully about what he does or she does now let's call this let's call some for these tools string for this ring new and passing it twice you see the problem what is the problem the we compile this function template where we did use T to be a string view that means the return type is string view that means that internally the plus operator creates a string but what we return is a view to this internally created string and at the moment we leave this function this object is gone so that we return a view to some data some character sequence that's not there anymore and the moment you use this you go run into a runtime error again if you're lucky you get a caught up ok so that's the danger with string view be very careful about that and by the way who made a mistake here how can we fix this how should we fix this template specialization yeah that's something for experts but it's easier now second parameter no return out oh yes how to solve the problem don't do that don't return a t return auto the compiler does better know than you what is returned here in the type system and it does return the right type so if you learnt the return type auto and you learn don't use it no that's not the rule for template if you use templates do it because the compiler knows better what the return type is at least if you don't directly return the T as it is so if you return some expression and to avoid some traps it's usually better to return auto instead of some template parameter T although this might make your code less readable but let's really rule is better than behaving err err honest so it also internally of course I shouldn't use a T here I have the same problem so also inside this generic code I should use an auto and then this code is safe okay some aspects of this first of all and you might say I have already code that uses a con string reference so let's overload it with string view you run immediately in problems because this is ambiguous if you pass a string at all so you might need more overloads just for string literals and we have that in the standard the z-buffer 17 standard has a problem with all these declarations they have run into this problem this is fixed in the meantime that we added more overloads there and this is fixed so this was ambiguous formally according to C++ 17 standard this but this will now compile so a few topics left parallel last year so I run algorithms in parallel this is an example I like so I've accumulate accumulate accumulates values the problem is it guarantees to accumulate from left to right which is not very helpful for parallel algorithms so how should we proceed here so how should we make it better well we can't restrict algorithms as they work so we introduce the new algorithm said there's reduce and with reduce you can say let's using multiple threads so that's run this in parallel so let's enroll in parallel accumulate the values and look at the accumulation the accumulation just take the existing value and adds the square of the next value so we would have to say we start with zero plus the square of 1 plus the square of 2 plus the square of 3 plus the square for that sequence 4 would accumulate to 30 and if we have 10 of them that should be 300 the return value and we get that if we call accumulate with 40 elements we get 300 and if we take reduce we take also we get also 300 as output value let's extend this test case to 1,000 elements surprisingly accumulating the same way all the where use is giving you 3 30,000 as a result for accumulating the elements here and reduce also gives you the same result that's um step 2 1 million elements so at 1 million elements accumulate gives you 30 million and reduce gives you Oh what's the problem here and why didn't we why do we get it now looks like overflow no it's not an overflow I use long-long long-long can hold these can hold 30 million as a value so what's wrong here well we made an error but we didn't see the error before because now accumulate guarantees to to apply the the credit card or the data function the operation from left to right but if we use multiple threads we have a problem think about taking the last two numbers and applying our function which means we add 3 plus the square of 4 which is 16 plus 3 which is 17 and then we go further to the left and say and now let's add 2 to it so let's add 2 with the result of 3 accumulated with 4 what happens is we compute the square of 4 again because the problem is this is not a commutative expression so the order in which we apply this to a sequence of values matters now the question is why do we see this problem just with 1 million elements why don't we see this problem with 10,000 elements or 1000 elements and the answer is that in the prowl algorithm world the implementers can decide what they do they don't have to compute things in parallel parallel is only hint saying hey you are allowed to compute this in parallel and this implementation where I found this problem this was a visual C++ they has implemented that to some extent of values to some number of values they still run in parallel only using one thread so they did the same as accumulate but the moment I had enough element they started to use multiple threads so multiple threats might and and problems due to motive threat and concurrency and undefined order there might just oq if you have enough elements so be where we were and in your test cases please use a lot of numbers and if you use the test cases if it's just 1,000 values it's not a proof that it's correct ok what should you do by the way that's exactly why we standardized transform reduce which is because we have to split two things we have to split to compute and parallel to compute the square of each value and then using multiple threads to add the resulting values and that's exactly why we have trans form reduce this will be the most important parallel STL algorithm we will have like MapReduce so it will just map the various transfer the various to some other value and then combine them in some fashion so last topic I think fine system library we adopted to spy system here's an example let's initialize a path check is it a regular file then use the file size or print the file size let's iterate over the elements in the directory if it's a directory and done otherwise check whether it exists this will code will compile as it did with boost with whose file system or with the experimental version of the stand up but there's a surprise so if you run this on to an end in a UNIX system the output is this so if I pass the root directory as argument I would iterate over the directories of the root so if I run this under Windows you get this if I pass C colon backslash which is which is yeah what happened here the boost fire they defy system library already in boost have this feature that when you print a pass it is quoted print quota and that means it has double quotes around and each backslash is escaped which is a nightmare for Windows because on Windows this looks very strange so we didn't fix that we found that that would be a huge problem too late so that's now under standard so what you should do is please if you print a path and you want to do it portable call path dot string to print it out as a string and you get one backslash for the windows word by the way can we do this a little bit bad yes and this will perform multiple operating system calls is regular file instance operating system calls its directory exists as an operating system call that's something you should avoid we have other options for that so for example there's a switch statement and you can ask for the status and of the path and then you get can ask for the type of this path and then you have just in the switch statement you switch between the different options and by the way here you see the not just the convention but you see here no where is it no no miss ok so what I wanted to say this switch statement is a new switch state and taking an initialization and then doing the switch so we have this new if a switch statement with initialization here good and for those who come from boost or for those who come from the experimental implementation of file system in the standard we changed some semantics please beware here is an program that that shows the difference if I call this program for user whole nickel dots gate for example in the past we defined that dot get is not a file name it's a name of the extension of a file name so the extension part of a file we fix that where C++ 17 when we adopted this to our to add two of the C++ standard and also as a consequence you could write loops here why we have a file name remove the file name and remove the fireman suddenly even we moved the slash so you could iterate over the path elements backwards with programming a loop this has both changed so dot git is now a file name not an extension it's a file name without an extension and the loop will no longer be used here it works more the way things should use self-explanatory so the point is if you just switch your old code to this new code please beware that just change the name space and recompiling it might change your behavior so we fix some things good that's it let's see last example I have a map I have a map mapping values to strings and I initialize this map with some customers what is the problem here well if you have this map each map element allocates memory and each memory is allocated somewhere in your program at somewhere in your memory so if you run this program with some extra statements you might find out that the elements themselves the key value pairs of a map long to a string I've distributed all over your memory which is a problem if you use cache lines and your bonnet cache all this data and you want to have it locally located together for that we have a new feature called polymorphic a memory resources so just by saying I have a memory pool and you can say I have a pool of values so I'm pooling new memory the moment I need it and suddenly your elements are located together in your memory roughly together so why is this an issue here's an interesting move I create an STD string but in my map there is called and PMR string so that the string also uses this local memory and is not distributed over memory again and just to make sure this move becomes a full copy because we are using a different memory pool so if you have strings now and you move them around and you move them to some other string type that use this approach of memory allocation please beware that your moves become strings and that might be counterproductive well for the whole optimization because suddenly this code will allocate memory each time we emplace this string the fix of causes that we should already declare s to be a PMR string okay that's it I ran out of time I have two or three others but that's that's fine so that's the traps I wanted to show you did somebody learn here something ok so it was helpful so this is at the end my advertisement we have cool new stuff in the C power 17 library some people complain that it's not very bringing us forward it's it's a mixture of a lot of new things all things a little bit small features not big features being revolutionary but applying them together is is something that is very cool but we have traps and you have seen almost all of them out not the biggest one I have if you want to read more you got for free as a conference attendee this book just to explain that this book is currently still written it's now has now 380 pages it's not done yet completely you would will get all updates for free if you download it and you just register with your yourself with your email address you will be get notifications or you should follow my Twitter account and I should also say that when we wrote the new edition of C++ templates and we published it in one and a half year ago we already took completely C++ 17 into account so there are two books that cover both of them and I'm still catching up I only have one year left before I start with a C++ 20 book this will be bigger I don't I know that already ok thank you very much I'm around here the whole day so give me feedback or ask some questions we are out of time so thank you very much and see you and have a good fun [Applause] you
Info
Channel: cpponsea
Views: 13,585
Rating: undefined out of 5
Keywords: c++, C++17, Nicolai Josuttis, Nicolai Josuttis c++, C++17 : The Biggest Traps, C++17 : The Biggest Traps - Nicolai Josuttis, C++17 The Biggest Traps - Nicolai Josuttis, C++ on Sea 2019, C++ on Sea, cpponsea, Folkestone, C++ Conference, Conference, Programming, c++ on sea 2019, c++ tutorial, new features of C++17, C++17 features, c++17 talk, programming traps, C++ talk, c++ talk video, c++17 masterclass, c++17 features review, c++ programming
Id: mAZyaAo3M70
Channel Id: undefined
Length: 80min 58sec (4858 seconds)
Published: Fri Feb 15 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.