- Custom Memory Manager & placement new - 13 Updates
- a problem about programming - 2 Updates
- Failed interview test - 1 Update
- boost::any and move constructible objects - 3 Updates
- What happens to unique_ptr -pointer in this case - 5 Updates
- Questions about custom allocator - 1 Update
| Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Nov 24 01:07PM On Thu, 24 Nov 2016 12:50:47 +0100 > similar enough): "C memory object and value semantics: the space of > de facto and ISO standards" > https://www.cl.cam.ac.uk/~pes20/cerberus/notes30.pdf This is a workaround for microsoft's compiler for its x86/64 products. You can do whatever that compiler and/or the x86/64 platform will accept. This also happens to be microsoft's suggestion (although I cannot say that I now remember what size of integer value they recommend). There are two issues, alignment and strict aliasing. On alignment, with x86/64 that only goes to speed of access and not correctness. An initial int value will in practice be fine. If you remain worried about it after testing, you can use an initial segment size of alignof(std::max_align_t) in which to put your integer value, which is 8 bytes on 32-bit windows and 16 bytes on 64-bit. Strict aliasing is not an issue. Just don't write or read the memory block's initial integer value in a way which breaks C++'s strict aliasing rules, that is, manipulate or read the initial integer value via char* or via std::memcpy()[1]. What else do you think is problematic? Incidentally gcc is irrelevant to this. It provides the correct value to operator delete[] to begin with. Chris [1] This is being pedantic. After casting operator delete[]'s void* argument to some other pointer type, type information about the dynamic type of the content is absent and indeed all objects formally in that block have already been destroyed, so microsoft's compiler cannot (and anyway will not) optimize against it. It is just a block of bytes at that stage, awaiting release to the memory pool or whatever else the custom allocator does. |
| mark <mark@invalid.invalid>: Nov 24 04:31PM +0100 On 2016-11-24 14:07, Chris Vine wrote: > strict aliasing rules, that is, manipulate or read the initial > integer value via char* or via std::memcpy()[1]. > What else do you think is problematic? Your delete receives a pointer to the middle of the memory block. If you do pointer arithmetic on that to get a pointer to the beginning of your memory block, you have undefined behavior. > [1] This is being pedantic. This is not being pedantic. Some compiler developers interpret the standard in an extremely aggressive (user-unfriendly) way and don't care if they break real-world code if they might get some tiny performance improvement. Maybe you can get away with this, if you restrict your code exclusively to VC++, but with GCC or CLang I wouldn't count on it. > pointer type, type information about the dynamic type of the content > is absent and indeed all objects formally in that block have already > been destroyed, The pointer origin and original type can still be tracked in a lot of cases and the compiler will be able to prove where the pointer came from. The delete[] operator will receive a pointer that pointed to some array S[]. The only pointer math that the standard allows is within the bounds of S[] and one beyond. Casting to char* doesn't allow you to go outside the bounds of S[]. Doing math on uintptr_t doesn't change that. > so microsoft's compiler cannot (and anyway will not) optimize against > it. VC++ is less aggressive with optimizations relying on undefined behavior, but is that guaranteed somewhere? |
| Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Nov 24 04:36PM On Thu, 24 Nov 2016 16:31:39 +0100 > On 2016-11-24 14:07, Chris Vine wrote: [snip] > Your delete receives a pointer to the middle of the memory block. If > you do pointer arithmetic on that to get a pointer to the beginning > of your memory block, you have undefined behavior. This is wrong. See below. > performance improvement. Maybe you can get away with this, if you > restrict your code exclusively to VC++, but with GCC or CLang I > wouldn't count on it. There is no getting away with anything if you access the integer value with unsigned char* or std::memcpy(). See below. But in any event, as I have already said, this is only concerned with VC++. There is no way you would bother with this rigmarole with either gcc or clang, which get operator delete[] right in the first place. > the bounds of S[] and one beyond. Casting to char* doesn't allow you > to go outside the bounds of S[]. Doing math on uintptr_t doesn't > change that. You are wrong about this. operator new[] and operator delete[] know nothing of types, whether array S[] or anything else. All they know about is raw bytes of allocated memory passed by void*. They are the equivalent of malloc() and free() (and many global operator new[] and operator delete[] just call malloc() and free() and do nothing else). I think you may be confusing this with the new[] and delete[] _expressions_, which do know about types and will construct or receive a typed array (albeit in the case under discussion beginning 4 bytes off the beginning of the raw memory originally allocated by operator new[]). By the time you get to operator delete[], the delete[] _expression_ has already destroyed all the objects in that typed array. All that is left as an object is the integer value that operator new[] originally put at the beginning of the allocated memory. You are doing three things in this putative operator delete[]: (i) you are working back to the beginning of the memory block originally allocated by your custom operator new[]. Using pointer arithmetic with unsigned char* is guaranteed by the standard to give the correct result and I cannot think why you think otherwise. At no point is any pointer going beyond the memory block allocated by operator new[], so there can be no undefined behaviour. (ii) you are then either using array addressing with unsigned char* or using std::memcpy() to copy the initial 4 bytes into a local unsigned int. If you are doubtful about the first, see §3.10/10 of C++11/14, last bullet. In practice use std::memcpy(), as in all decent compilers the call will be optimized out - it is the recommended way of dealing with aliasing for this kind of case. (iii) you then deallocate this raw block of memory with your custom deallocator. > > against it. > VC++ is less aggressive with optimizations relying on undefined > behavior, but is that guaranteed somewhere? If you use either unsigned char* or std::memcpy() the aliasing is entirely compliant and guaranteed to work. See §3.10/10 of C++11/14 as regards unsigned char*. |
| Jorgen Grahn <grahn+nntp@snipabacken.se>: Nov 24 05:28PM On Tue, 2016-11-22, Mark Blair wrote: >>>> What kind of scenarios are there with modern hardware and software where >>>> you are using this kind of thing? >>> It's very useful when used with shared memory. ... > On Linux, between separate processes that share structured data. In one > case we have a Java GUI that uses JNI to interface to data populated > from a C++ daemon. I think I'd tend to treat that memory as an unstructured buffer, or at most a dumb struct. The object wrapping it could have pointer to the buffer, rather than be created using placement new. The class would have to be very carefully written anyway. I'm like Christopher -- never used placement new, never think I will. If I have to, I'll read up on it, like with so many other language features. /Jorgen -- // Jorgen Grahn <grahn@ Oo o. . . \X/ snipabacken.se> O o . |
| mark <mark@invalid.invalid>: Nov 24 07:04PM +0100 On 2016-11-24 17:36, Chris Vine wrote: > typed array (albeit in the case under discussion beginning 4 bytes off > the beginning of the raw memory originally allocated by operator > new[]). No, I don't. > _expression_ has already destroyed all the objects in that typed array. > All that is left as an object is the integer value that operator new[] > originally put at the beginning of the allocated memory. That doesn't change the fact that it was an S[]. If all the code is available inline, the compiler can infer that it was an S[], it didn't change the type to something else and it thus cannot be a pointer into a larger char array. And you cannot leave the bounds of S[]. There is no special allowance for pointer math for delete[] in the standard. > originally allocated by your custom operator new[]. Using pointer > arithmetic with unsigned char* is guaranteed by the standard to give the > correct result and I cannot think why you think otherwise. It gives the numerically correct result. > At no point is any pointer going beyond the memory block allocated by > operator new[], so there can be no undefined behaviour. It goes beyond the bounds of S[] - which it was just previously. How does that pointer that pointed to S[] magically change to a pointer for the outer block? That it was auto-cast to void* doesn't change it's history or provenance. What do you think the dynamic type of a pointer to an S[] is over its lifetime (after construction, after the destructor has run, when the deallocation function is called)? If the dynamic type changes, how does that happen? Where in the standard is that spelled out? Do you think your pointer math is valid over the entire lifetime of S[] or only after the destructor has run? |
| Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Nov 24 06:22PM On Thu, 24 Nov 2016 19:04:01 +0100 > does that happen? Where in the standard is that spelled out? > Do you think your pointer math is valid over the entire lifetime of > S[] or only after the destructor has run? You say you are not confusing operator new[] and delete[] with the new[] and delete[] expressions, but you are contradicting yourself because you plainly are. operator new[] and operator delete[] do not deal in arrays. They deal in bytes of memory. End of story. |
| legalize+jeeves@mail.xmission.com (Richard): Nov 24 06:27PM [Please do not mail me a copy of your followup] Louis Krupp <lkrupp@nospam.pssw.com.invalid> spake the secret code >object, whether it's static or automatic or allocated by something >other than malloc(), you can use placement new to run the object's >constructor with calling malloc(). I've also seen it used to ensure that the memory allocation happens inside a DLL but the constructor runs in whatever code is creating the instance of a type defined by the DLL. This is a Windows annoyance of how DLLs behave, but if you have this problem then it is something you have to deal with. Another alternative is to expose only interfaces with virtual d'tors and provide factory functions for creating instances. (Gee, sounds like COM!) -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Terminals Wiki <http://terminals-wiki.org> The Computer Graphics Museum <http://computergraphicsmuseum.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| legalize+jeeves@mail.xmission.com (Richard): Nov 24 06:38PM [Please do not mail me a copy of your followup] someone@somewhere.net spake the secret code >What does one use shared memory for? Faster IPC? What are some examples? >where you've used it? Yes, faster IPC. If two processes are on the same machine, they don't need sockets to talk, just a shared memory segment. If you're using some flavor of unix, the X Window System has had this for a very long time to enable more efficient communication between clients running on the same machine as the display server. >Only virtual addresses that map to regions of a file. If that is >correct, perhaps, this is the reason I have not come across this, being >a Windows guy. It's the same thing under a different name: <https://msdn.microsoft.com/en-us/library/windows/desktop/aa366551(v=vs.85).aspx> MSDN calls it "memory-mapped files that the system paging file stores". If you look at that example, there is no actual file specified, which tells Windows "use the paging file", which is just a slightly obtuse way of saying "give me a piece of virtual memory as a file". That's because Windows is going to expose the sharing through file handles so we need some way of getting a file handle for a chunk of virtual memory in order to share a memory segment. They use a "filename" (Global\\MyFileMappingObject) to identify the segment between the two processes. There are analogous APIs in unix for all of this and the result is basically the same. You have a chunk of memory that can be shared between two processes. For System V shared memory segments (I don't think BSD ever came up with something that did the same thing), you want to look at functions shmget, shmat, shmctl, etc. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Terminals Wiki <http://terminals-wiki.org> The Computer Graphics Museum <http://computergraphicsmuseum.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| legalize+jeeves@mail.xmission.com (Richard): Nov 24 06:41PM [Please do not mail me a copy of your followup] Jorgen Grahn <grahn+nntp@snipabacken.se> spake the secret code >most a dumb struct. The object wrapping it could have pointer to the >buffer, rather than be created using placement new. The class would >have to be very carefully written anyway. All the instances I've seen treat the chunk of memory as a binary buffer into which structures are serialized/deserialized. Going back to the X Window System situation, this makes the perfect place for the client to write its protocol stream and for the server to read the protocol stream. They just don't communicate the protocol bytes over a socket. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Terminals Wiki <http://terminals-wiki.org> The Computer Graphics Museum <http://computergraphicsmuseum.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| mark <mark@invalid.invalid>: Nov 24 08:04PM +0100 On 2016-11-24 19:22, Chris Vine wrote: > new[] and delete[] expressions, but you are contradicting yourself > because you plainly are. operator new[] and operator delete[] do not > deal in arrays. They deal in bytes of memory. End of story. There are no bytes of memory in the standard. Pointer arithmetic is only allowed within array bounds. Read the standard, "5.7 Additive operators [expr.add]". Either that void* is a pointer into an array of some type (that array must include your size variable) or your pointer math is undefined behavior. |
| Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Nov 24 08:15PM On Thu, 24 Nov 2016 20:04:47 +0100 mark <mark@invalid.invalid> wrot [snip] > Either that void* is a pointer into an array of some type (that array > must include your size variable) or your pointer math is undefined > behavior. Despite all the previous things you have posted this is an interesting point. I suppose the first thing to say is that contrary to what you say there are bytes of memory in the standard. They are referred to as a "block of storage" having a length in bytes. Let's take the return value of operators new and new[]. §3.7.4.1/2 of the standard says: "If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. ... The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function)." It therefore provides for the block of storage returned by void* by operator new/new[] to be convertible to, amongst any other type, pointer to unsigned char or pointer to unsigned int and then used to access an object or array of objects of those types in the storage. It must allow this, or the there would be no purpose in allocating memory in the first place. Upon constructing objects in that block of storage, they becomes assessible and, if it is an array, §5.7 applies. Objects or arrays are placed into the block of storage by placement new or (for trivial types) just by memcpy()ing it in. That is what the new/new[] expression (as opposed to operator new) does. Arguably on the most pedantic level, when implementing the microsoft workaround you should place the integer value into the memory as an array of char, so that subsequently using pointer arithmetic on it would then meet the requirements of §5.7. As a matter of fact the microsoft workaround does not (as I recall it) require one to go to the additional trouble of converting to an array of char. The overarching point on that is, as I have now said for the third time, that this is only concerned with VC++ and is the way they deal with the issue. One can therefore take it that, with VC++, it works. And for the reasons mentioned it seems to me that it is required to work. |
| Paavo Helde <myfirstname@osa.pri.ee>: Nov 24 10:19PM +0200 On 24.11.2016 13:50, mark wrote: > "C memory object and value semantics: the space of de facto and ISO > standards" > https://www.cl.cam.ac.uk/~pes20/cerberus/notes30.pdf The technique described by Chris is actually often used by compilers themselves. When you delete[] an array allocated via custom memory allocator, the compiler needs to know how many destructors to call. How does it know? Simple, it asks for 4 or 8 bytes more memory from your custom allocator, stores the object count in the beginning of the block and shifts the pointer before returning it from the new[] - that's the same technique as described by Chris. Of course, implementation can do things which are forbidden for mere mortals, but it still shows the memory is not considered as pure array of N objects. Demo: MSVC2013: #include <iostream> #include <string> class A { public: void* operator new[](size_t n) { void* p = malloc(n); std::cout << "Allocating " << n << " bytes at " << p << "\n"; return p; } void operator delete[](void* p) { std::cout << "Releasing " << p << "\n"; } std::string s; }; int main() { std::cout << "Creating array of 10 strings (" << 10*sizeof(std::string) << " bytes)\n"; A* a = new A[10]; std::cout << "Array starts at " << a << "\n"; delete[] a; } Creating array of 10 strings (400 bytes) Allocating 408 bytes at 00000000000EB5E0 Array starts at 00000000000EB5E8 Releasing 00000000000EB5E0 |
| Jerry Stuckle <jstucklex@attglobal.net>: Nov 24 03:58PM -0500 On 11/24/2016 1:38 PM, Richard wrote: > some flavor of unix, the X Window System has had this for a very long > time to enable more efficient communication between clients running on > the same machine as the display server. Don't forget if any process writes to the shared memory, all access to the shared memory must be protected by mutex semaphores. That slows things down a bit - but it's still a lot faster than any other IPC. -- ================== Remove the "x" from my email address Jerry Stuckle jstucklex@attglobal.net ================== |
| Louis Krupp <lkrupp@nospam.pssw.com.invalid>: Nov 24 09:46AM -0700 >[https://www.amibroker.com/guide/afl/strright.html] >-Lenght is a lenght (number) of the sting of the symbol (StrLeng) >http://www.amibroker.com/guide/afl/strlen.html Your program, as you last posted it: AB = CreateObject("Broker.Application"); sts = AB.Stocks(); Qty = sts.Count; for( i = Qty - 1; i >= 0; i = i - 1 ) { st = sts.Item( i ); Ticker = st.Ticker; printf("changing " + Ticker + "\n" ); Length = StrLen(Ticker ); ** 'Length' is now set to the length of the symbol if( StrFind(Ticker, "TO:") ) st.Ticker= "TC-"+StrRight(Ticker,Lenght+3); ** 'Lenght' is not the same as 'Length'. My guess is that 'Lenght' is ** zero. ** ** I would try this: ** st.Ticker= "TC-"+StrRight(Ticker,Length - 3); } Let me know what happens. Louis |
| legalize+jeeves@mail.xmission.com (Richard): Nov 24 06:24PM [Please do not mail me a copy of your followup] Lino <lino@net.com> spake the secret code >Script Below Wrong newsgroup. Try a forum for Windows Scripting. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Terminals Wiki <http://terminals-wiki.org> The Computer Graphics Museum <http://computergraphicsmuseum.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| legalize+jeeves@mail.xmission.com (Richard): Nov 24 06:23PM [Please do not mail me a copy of your followup] "Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com> spake the secret code >Just trust me on this. Or just KILL file Stefan Ram and this newsgroup becomes more enjoyable. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Terminals Wiki <http://terminals-wiki.org> The Computer Graphics Museum <http://computergraphicsmuseum.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| bitrex <bitrex@de.lete.earthlink.net>: Nov 24 11:13AM -0500 From this it looks like boost::any/std::any currently only supports objects which are copy-constructible, and not simply move-constructible. https://www.reddit.com/r/cpp/comments/4fyt3v/why_doesnt_stdany_support_move_only_types/ I noticed from the boost/any.hpp that if I'm using C++11 or greater, the code first checks to see if the class is copy-constructible, and if it is it then checks the C++ version to see if it's C++11 or greater (#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES) and it looks like it then uses the move constructor anyway. That's a bit annoying. Is writing a wrapper class the only workaround? |
| "Öö Tiib" <ootiib@hot.ee>: Nov 24 09:07AM -0800 On Thursday, 24 November 2016 18:14:07 UTC+2, bitrex wrote: > (#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES) and it looks like it then > uses the move constructor anyway. > That's a bit annoying. Is writing a wrapper class the only workaround? Why it is annoying? Things that are not copyable are usually passed around by additional indirection (like smart pointer or handle) anyway. So one workaround is to put that thing into that 'any' instead of object itself. Other workaround is not to use 'any'. It is indeed safer than things like void* or ellipsis function argument but so what? I never need these either. |
| bitrex <bitrex@de.lete.earthlink.net>: Nov 24 12:35PM -0500 On 11/24/2016 12:07 PM, Öö Tiib wrote: > Things that are not copyable are usually passed around by additional > indirection (like smart pointer or handle) anyway. So one workaround > is to put that thing into that 'any' instead of object itself. Hmm, yes. I guess that would make sense, wouldn't it ;-) > Other workaround is not to use 'any'. It is indeed safer than things > like void* or ellipsis function argument but so what? I never need > these either. It looks like it's about 1000 times slower in practice than say, boost::variant too. I was mostly hoping to use it as an argument to a constructor so I could just pass in a ref to (any) object and extract a particular method from that object into a boost::function. Should work just fine passing in the smart pointer I guess |
| JiiPee <no@notvalid.com>: Nov 24 12:42PM Ok, this was pretty clear explanation. So in this occasion its ok because MFC uses that convention. On 24/11/2016 10:08, Chris Vine wrote: |
| Jerry Stuckle <jstucklex@attglobal.net>: Nov 24 09:38AM -0500 On 11/24/2016 7:35 AM, JiiPee wrote: > the function will destroy it. Is there a risk here and confusion that if > the user forgots that its self deleted? How does the user remember that > in this occasion the object will be self-destroyed? It's called documentation. And a user can forget anything is deleted. It has nothing to do with whether it is self-deleted or not. You must also remember that this is in response to an asynchronous call from within Windows, not from the application itself. The object cannot be deleted before this because it must be there to handle the message. And the object is already considered "gone" to the rest of the application. -- ================== Remove the "x" from my email address Jerry Stuckle jstucklex@attglobal.net ================== |
| Paavo Helde <myfirstname@osa.pri.ee>: Nov 24 06:03PM +0200 On 24.11.2016 14:35, JiiPee wrote: >> should never be referenced again (and isn't in MFC). > but even if it is not a smart pointer, the user must still *know* that > the function will destroy it. Is there a risk here and confusion that if Confusion is the middle name of MFC! :-) > the user forgots that its self deleted? How does the user remember that > in this occasion the object will be self-destroyed? What do you mean by "user"? If you mean the programmer, then he is supposed to follow the documentation. If it is the calling code, then this calling code is MFC which *assumes* that dialog will be dead after OnNcDestroy(). This function should not be called explicitly from elsewhere than MFC. Besides, MSDN documentation says that 'delete this;' should be put into virtual PostNcDestroy() override which is called from the default implementation of OnNcDestroy(), so whoever placed it directly in OnNcDestroy() already violated the conventions and caused confusion indeed. "https://msdn.microsoft.com/library/49a832ee-bc34-4126-88b3-bc1d9974f6c4.aspx#cwnd__onncdestroy" |
| Paavo Helde <myfirstname@osa.pri.ee>: Nov 24 06:11PM +0200 On 24.11.2016 14:37, JiiPee wrote: >> With legacy frameworks like MFC one needs to use their conventions of >> course, and if this requires writing 'delete this;' then so be it. > You would not mix the modern C++ with MFC? Sure one can mix code, but one cannot redesign MFC to use another ownership model for its objects. |
| JiiPee <no@notvalid.com>: Nov 24 04:50PM On 24/11/2016 16:03, Paavo Helde wrote: >> but even if it is not a smart pointer, the user must still *know* that >> the function will destroy it. Is there a risk here and confusion that if > Confusion is the middle name of MFC! :-) hehe. Well, I still like it (even if its not perfect/pretty). Remember, i have lived my whole programming life with it!! And I dont like divorces, hehe. People take divorces too easily.. I am a faithfull man :). >> the user forgots that its self deleted? How does the user remember that >> in this occasion the object will be self-destroyed? > What do you mean by "user"? the one who uses that Dialog. The programmer who writes code to use that dialog. > If you mean the programmer, then he is supposed to follow the > documentation. If it is the calling code, then this calling code is > MFC which *assumes* that dialog will be dead after OnNcDestroy(). but the question is that do we delete the dialog object inside the dialog class (at OnNcDestroy call) or from the class where it is created. But seems like Microsoft supports this... below: > into virtual PostNcDestroy() override which is called from the default > implementation of OnNcDestroy(), so whoever placed it directly in > OnNcDestroy() Good catch. Oh, now i get it. I was capturing the message directly, but I should use the virtual call. Ok, but online many people seem to use directly OnNcDestroy(); message function. > already violated the conventions and caused confusion indeed. > "https://msdn.microsoft.com/library/49a832ee-bc34-4126-88b3-bc1d9974f6c4.aspx#cwnd__onncdestroy" but the confusion I mentioned is more like with the delete this; |
| mark <mark@invalid.invalid>: Nov 24 01:38PM +0100 On 2016-11-24 13:09, Christopher J. Pisz wrote: > Deallocated 16 bytes for a struct std::_Container_proxy > Why are 16 bytes allocated before space for the actual data I am putting > into the std::vector and what is a container_proxy? Probably iterator tracking / debugging: <https://hadibrais.wordpress.com/2013/11/13/dissecting-the-c-stl-vector-part-2-constructors/> > What is the second argument in pointer allocate(size_type n, > const_pointer = 0) ? > What is the second argument in void deallocate(pointer p, size_type) ? http://en.cppreference.com/w/cpp/memory/allocator > { > return static_cast<size_type>(-1) / sizeof(value_type); > } For unsigned types, -1 is equivalent to numeric_limits<size_type>::max(). |
| You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment