Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
That Turkey Story
Mar 30, 2003

Some more links:
The C++ Standards Committee
The C++ Source
Overview of Generic Programming
Exception Safety

Adbot
ADBOT LOVES YOU

That Turkey Story
Mar 30, 2003

mracer posted:

Ok, more of a Visual Studio question. Whats the best way to step thru code with many macros. you know #define functions.

for example:
code:
#define COPY_PMV(adst,asrc) {                      \
  Ipp32s nn;                                       \
  for(nn=0; nn<sizeof(adst)/sizeof(Ipp32s); nn++)  \
    ((Ipp32s*)adst)[nn] = ((Ipp32s*)asrc)[nn];     \
}
is there an option for the debugger to show that instead of COPY_PMV(pointer,pointer) ?

For things like what you just posted you shouldn't really be using macros, just use inline functions, unless you want the code strictly C89. I don't know offhand if there is some kind of trick in Visual Studio to let you step through macros, but one portable trick that will work in all IDEs is to instead implement your macro as a #include (though you usually only want to do this with very long macros, especially if there is further preprocessor iteration during invocation).

For instance:
code:
/*******************************/
/* COPY_PMV.h */

Ipp32s nn;
for(nn=0; nn<sizeof(PARAM_adst)/sizeof(Ipp32s); nn++)
  ((Ipp32s*)PARAM_adst)[nn] = ((Ipp32s*)PARAM_asrc)[nn];

#undef PARAM_asrc
#undef PARAM_adst

/*******************************/

/*******************************/
/* Some other file */

void function()
{
  /* bunch of stuff here */

  #define PARAM_adst /* whatever */
  #define PARAM_asrc /* whatever */

  #include "COPY_PMV.h"
}

/*******************************/
Now when you step through you will be taken to what used to be a macro definition (and is now just the body of an included file). Again, I wouldn't do this for that particular example, but for complex macros that can't be turned into functions this can be very handy. It's hacky but it works.

Remulak posted:

What's bugging me now is package handling - ideally I need to deal with a zip file with some pictures, binary resources, text, and metadata. My first thought was OpenXML, specifically Open Packaging Convention, which is MADE for this and has a nice API. My second thought was *oh gently caress*, this won't work on the Mac. While it's submitted for standardization it doesn't look like there's a Mac or specific cross-platform C++ approach.

Is there a good, standard way to deal with this kind of problem from non-platform-specific C++?
I'm pretty sure Avenging Dentist is working on a cross-platform library like that right now, but I don't know how far along it is.

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

Also, in trying to get code like that shown above to compile I was meet with this error from gcc 4.0.0:
code:
stl_algo.h: In function `_OutputIterator std::merge(_InputIterator1
	_InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator)
	[with _InputIterator1 = int*, _InputIterator2 = int*, 
	_OutputIterator = std::insert_iterator<my_container>]':
	
main.cpp:183:   instantiated from here

stl_algo.h:2998: error: no match for 'operator=' in 
	'__result. std::insert_iterator<_Container>::operator*
	[with _Container = my_container]() = * __first2'
	
stl_iterator.h:531: note: candidates are: std::insert_iterator<_Container>& 
	std::insert_iterator<_Container>::operator=(typename _Container::const_reference)
	[with _Container = my_container]

stl_iterator.h:491: note:                 std::insert_iterator<my_container>& 
	std::insert_iterator<my_container>::operator=(const std::insert_iterator<my_container>&)

From the error it looks like you probably either did not define my_container::const_reference or you defined it incorrectly [Edit: Actually, it sounds only like the latter, not the former, since otherwise you'd get an error during instantiation of insert_iterator, not during the call]. When you are implementing custom containers and iterators and want to use them with standard library algorithms, just always make them compliant and you'll avoid issues like this. It's not much more effort than just adding proper typedefs/traits.

That Turkey Story fucked around with this message at 00:39 on Feb 18, 2008

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

Hrm, that may be the case but then is this not correct?
code:
typedef my_container& reference;
typedef const my_container& const_reference;
No. reference and const_reference are references to the value_type, not references to the container type. You should really get used to reading the error messages -- they are wordy, but everything you need to know about what you did wrong is there.

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

:downs: Thanks, that certainly clears up that mess. Not sure how you deciphered the exact problem from that error though, here's what my thought process was, line by line:

...

...

3. In the STL code there's some operator*() calling a operator=() that either isn't defined or is bad. Note: Reread this error message, it's not operator*() calling operator=()...

4. One operator=() takes typename my_container::const_reference.
4a. Check that I have typedef for const_reference, I do, ok.

5. The other operator=() is just the copy-assignment operator for std::insert_iterator<my_container>

...

It never occurred to me that my_container::const_reference was wrong, though I can see now how that would logically be something further to check. And this error:
code:
stl_algo.h:2998: error: [b]no match for 'operator=' ... [/b]
was a bit confusing so I wasn't even sure my reasoning about what the problem was was entirely correct. What's up with the __result. and * __first2? I'm not sure how to read that.

You read it almost fully and were just about there but then you stopped! You already narrowed it down to pretty much being that the only thing your code was providing at compile-time here which was even showing up in the message was const_reference (sometimes it's a little bit harder than this and what you did wrong won't be mentioned explicitly). The main thing that tells you that it definitely was const_reference being improperly defined was:

code:
stl_algo.h: In function `_OutputIterator std::merge(_InputIterator1
	_InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator)
	[with _InputIterator1 = int*, _InputIterator2 = int*, 
	_OutputIterator = std::insert_iterator<my_container>]':
	
main.cpp:183:   instantiated from here

stl_algo.h:2998: error: no match for 'operator=' in 
	'__result. std::insert_iterator<_Container>::operator*
	[with _Container = my_container]() = * __first2'
	
stl_iterator.h:531: note: candidates are: std::insert_iterator<_Container>& 
	std::insert_iterator<_Container>::operator=(typename _Container::const_reference)
	[with _Container = my_container]

stl_iterator.h:491: note:                 std::insert_iterator<my_container>& 
	std::insert_iterator<my_container>::operator=(const std::insert_iterator<my_container>&)
You kind of went through some of this already but I'll go through exactly the thought process I try to go through.

From the top, you know it's an error directly in the merge algorithm. It's extremely unlikely that your library's standard merge algorithm is implemented incorrectly and you are using a good compiler, so it's probably not some weird compliance problem. Assume it's how you're using it. The compiler tells you what types you used for iterators, which were just int*, so you can safely rule out that being the problem since pointers are valid iterators right out of the box, at least concerning compile-time type requirements (and since this is a compile-time error, that's all that would matter here).

The only other option is it's something wrong with your last type, std::insert_iterator<my_container>. Again, since insert_iterator is standard, it's very unlikely it's a problem in insert_iterator itself, so you probably are using it with a type that doesn't properly meet its requirements.

So then you get to:

code:
error: no match for 'operator=' in 
	'__result. std::insert_iterator<_Container>::operator*
	[with _Container = my_container]() = * __first2'
Which says that it's dereferencing and instance of insert_iterator<my_container> and then uses assignment to set it to *__first2. So in other words, you could imagine a hypothetical line:

code:
void foo( std::insert_iterator< my_container > your_insert_iterator, some_type __first2 )
{
  *your_insert_iterator = *__first2; // This line is where the error would be
}
Without going any further, if you know insert_iterator, you know that operator* just returns *this, so the problem is that there is no match for a plain-old call to = of insert_iterator< my_container >. If you didn't already know all of insert_iterator's assignment operators it lists them in the same error. The only two are the copy-assignment, which clearly can't be the problem, especially not from the compile-time error you're getting, and the one taking my_container::const_reference. This should jump out at you. Looking again at the context of the error message it is now equivalent type-wise to:

code:
your_insert_iterator = *__first2
You said you didn't know what __first2 was. Depending on what IDE you're using, you can likely just double-click your error message and it will take you to that line in code where you can see the type, or go to the line manually since it's listed next to the error (the type is all you need to know here since it's a compile-time error), but without even doing that, look at the context, *__first2 looks suspiciously like an iterator, which makes sense since this is a merge algorithm and you passed in iterators to it. In fact, "first", "middle", "last", "result", and "comp" are very often the parameter names used with standard library algorithms (and you may even be reminded of that when you type in "merge(" since most IDEs will pop up the signatures of the algorithm at that point).

Even if you didn't realize that immediately, still without following the link to the line in code, it looks fairly clear that *__first2 is probably an iterator being dereferenced and you passed in int* objects for iterators, so the statement is very likely to be similar type-wise to:

code:
int value;
your_insert_iterator = value;
Since we already narrowed down the problem to being that no assignment operator had valid parameters for that and that the one likely to be the problem was the one with my_container::const_reference as the parameter, from the context you can now see that const_reference should most likely be a reference to a const int here. Looking at your definition, you realize that you defined const_reference as a reference to the container rather than a reference to the value_type, which if you didn't already realize was wrong, should look at whatever you are using as a resource to double-check what its implementation should be, and short of that, you can just compare it to other standard containers.

Apart from that, you should usually try to be conscious of rationale when writing code. Having a nested const_reference type be a reference to the type it is a member of probably isn't too useful so that should maybe set off some bells when you are writing it. If you don't understand the design rationale behind the code you are using, you should try to learn it both so that you may use the code the way it was intended and so that you can use similar rationale in your own designs, especially if it is an extension of what is already written.

Really though, when making your container standards compliant you should have a good book or the standard at hand to tell you the requirements to begin with which would have helped you avoid this problem form the start (although, as a side note, requirements will all be explicit in code with C++0x which will pretty much even remove the need for any resource at all when making your types meet proper requirements, since type requirements along with axioms are a part of the code itself... with the exception of complexity requirements...).

Lexical Unit posted:

And when you say "get used to reading the error messages" do you mean simply from continued experience, or is there a better way you can suggest?
Mostly just from being confident in what you know about the language and making sure you understand fully what each part of the error says. They are usually wordy but overall not too complex. The wordiness just comes from telling you template arguments and other information which you may or may not need so that you can see if you made a mistake with your types. They're more just intimidating than anything else but underneath they're very often not much more complex than a regular error.

That Turkey Story
Mar 30, 2003

Avenging Dentist posted:

Well yes, that's the point. The example is greatly simplified; the original involved a complicated chain of template instantiations and was non-obvious, especially since it broke in a completely separate area (the typedef'd template was instantiated with base::foo and all subsequent references to that template, typedef'd or not, used that instantiation).

Are you saying that the base class was dependent on a template argument of the child? If so, on a compliant compiler you should have gotten an error at the typedef which would have probably saved you some trouble (since foo there isn't a dependent name you'd get error in the first phase before you even instantiate the template, but vc++ won't catch this because it incorrectly pushes everything off to instantiation). It always helps to use gcc instead of vc++ when doing template stuff.

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

Contrived example ahoy!

That's entirely a different issue -- your problem there is that you're misusing the size type for something other than its actual purpose. The size type is the unsigned counter-part of the container's iterator difference type and is used for things like the number of elements in the container, unrelated to that "weird_thing" size_type in your example.

As for your original question, again, just use the unsigned counterpart of your iterator difference type. Usually these are std::ptrdiff_t and std::size_t respectively, but they might be different depending on your container's needs.

That Turkey Story
Mar 30, 2003

illamint posted:

I see that there's Boost.Regex, but we have to hand in our source code at the end of the project and I'm not sure that it's feasible to merge the Boost shared library stuff into my makefiles just for this. Should I just parse it by hand? Is there a better solution? I'm used to being able to just go to the Java or Python docs and glance at the syntax in the manual but I'm having kindof a hard time doing that with C++.

There's also regex in TR1 that's based off of Boost.Regex and is probably supported by your compiler's standard library already.

That Turkey Story
Mar 30, 2003

very posted:

Why is my "this" pointer changing on every line of execution?

My program is crashing because this is changing to something weird like 0x00000001 or 0xffffffff or random junk as I step through the program. The functions worked fine with an earlier build but now I am converting everything over to use wxWidgets and now this is happening all over the place.

I am having a lot of trouble searching as "this" isn't exactly a unique word.

Your this pointer should never change. If you are in a member function and the this pointer is one value, then later on in the same function, this is a different value, then you are either writing past the end of a buffer that you allocated on the stack or you are writing to a memory location on the stack via a dangling pointer to something that used to be on the stack but has now left scope.

If there is no code that could be writing values as you step through your code, but this is still changing between lines, then you may have passed a pointer to a stack-allocated variable to another thread (i.e. via a call to some library you are using) and the value is being changed from that thread even though your local object had since left scope, meaning it is writing to whatever happens to be in that memory location now, which in this case is possibly your this pointer.

FatCow posted:

I'm using a stl set to hold multiple instances of a class. Is it considered poor form to make things 'mutable' that aren't used in the comparison operator to get around the implicit cast to const?

It's kind of hackish, have you considered using a map instead or are you unable to partition the type? If you have Boost you might also want to consider using a Multi-Index Container and modify the value with its "modify" function template.

That Turkey Story
Mar 30, 2003

Yes.

That Turkey Story
Mar 30, 2003

CasualCliffy posted:

I'm trying to do the following:

code:
template <typename T>
class Foo
{
public:
    Foo ();
private:
    friend class T;
};
but according to the standard, you cannot declare a template parameter to be a friend. I'm trying to figure out the best way to do this without using friend. The reason I wanted to do this is that T will be a policy type, and having access to Foo's guts is natural. Any ideas?

If you really want this, put the implementation of your class into a private base, then, from inside Foo, pass off that base to T so that it may access the implementation.

That Turkey Story
Mar 30, 2003

Plastic Jesus posted:

Yes, Rob Pike is an idiot.

I don't care who said it, good programming is not authoritative and that is some of the worst advice I've ever heard. It could come from the mouths of K&R and it'd still be garbage. In fact, it is so terrible that when I first read the quote I assumed it had to be a joke!

Plastic Jesus posted:

What I generally do now is have a monolithic include file to distribute with my libraries, but within each project I adhere to Pike's Law. One distinct advantage that's come of this is is a better understanding of which components use which other components, making bad design is much more blatant. Note also that 95% of my code is c, not c++, so it's easier to get away with this approach.

You don't think that adhering to this "law" is a bad design on it's own!?

:psyduck:

You don't force users to manually include all dependencies for lots of reasons, even apart from the obvious tediousness and room for error. Forcing other programmers to include the dependencies also means that the user has to worry about changing all of the files that include your header if dependencies are added or removed, not to mention the fact that you are requiring the user to know details that are specific to an implementation when in actuality they can be made entirely transparent. I can only imagine what would happen if you were working on a cross-platform project where different system headers had to be included depending on the operating system -- now all of a sudden the user of the header file has to do a preprocessor check for what OS they are targeting to figure out what additional header files they need to include. Dependencies should be automatically included, especially when they potentially vary depending on implementation.

Plastic Jesus posted:

The real question is, how pissed off is the next developer to take over my projects going to be? Very, I assume.

Most likely they would hunt you down and kill you. At least that's what I would probably do.

That Turkey Story
Mar 30, 2003

The Red Baron posted:

I'm wondering if it's at all possible to further specialize a nested class of a base class in its subclass(es)?

What I want is something that works theoretically like this (ignore the actual types used, they're not important):

Not in standard C++, however the VC++ compilers have a little hacky way to make it work which is actually what is used to provide VC++ with native support for Boost.Typeof.

That Turkey Story
Mar 30, 2003

Scaevolus posted:

This is a dumb question, but were most of these vectors representing triples? (<x,y,z>)

Oh my lord... I hope that isn't what he was using std::vector for!

That Turkey Story
Mar 30, 2003

vanjalolz posted:

Ha Ha what the hell? Do Boost pointers actually add anything useful to warrant such terrible syntax?

They're not different pointer types, it's just a metafunction that yields a regular pointer.

Avenging Dentist posted:

Quick question, which hopefully That Turkey Story can answer: is there a library to support properties in C++ (a la C#)? If not, I'm going to write one...

I though I had this discussion with you but maybe not. I actually made a library for properties in C++ a couple years ago but it's impossible to make one without overhead except in certain cases unless you complicate your encapsulating class definition (and in the case of empty types, you're out of luck). The reason is that if you want the standard . syntax, you generally have to make a dummy object that needs access to the encapsulating class, which you can't directly do without storing redundant information.

A quick example of a common situation that causes problems is a container with begin, end, and size properties. To get around having to have the internal property object need access to the encapsulating object to pull out iterator data, you can just store the begin iterator inside of the begin property and store the end iterator inside of the end property, but then what do you do for size? Often size isn't explicitly store in containers at all and is instead just calculated on demand, such as internally doing end() - begin() when you call size() for std::vector (which is very common). The issue is, how does your size property dummy object get access to the begin and end iterators? It can't directly access them because they are contained in some object other than itself that it does not directly or indirectly encapsulate. Since you are going to be using overloaded operator = and an overloaded conversion operator for access and because . itself is not overloadable, you have no way of passing a reference to the data transparently during the access. Instead you have to do something like always store a reference to the encapsulating object from within your property, which just needlessly bloats the size of your encapsulating object and also adds some additional overhead such as for initalization. Not only that, but the maker of the type now needs to explicitly initialize the property in his constructor, which makes his own code more complex.

Another trivial example that causes problems is a bunch of properties that map into a bit-field. You can't stick part of the bit-field in each member, so for each of those properties, you're going to be taking some damage. Now all of a sudden you need O(N) redundant self-references stored indirectly in your object, where N is the number of bits you are accessing, with each property generally taking sizeof(void*) space a piece, and each needing to be separately initialized in the constructor.

Then there is the issue of properties with virtual accessors. If you accept all of the problems mentioned above and continue on, virtual functions add even more hassles. Why? Because now the implementation of your accesses needs to be a call that is forwarded to a virtual function in your encapsulating object, since if you just directly make the functions virtual in your property dummy object, deriving from the encapsulating object won't allow you to override your property's virtual functions (and even if you could, it would still imply that each of your properties with virtual accessors has its own vtable pointer, which again would cause hell to the size and initialization of your overall object).

All of these problems are pretty much show-stoppers, at least with the common C++ philosophy. If you still want to go on, what I ended up doing, just because I refused to be defeated and wanted to see something remotely similar to properties even though I'd never actually use it, is overload a binary operator for accessing the name of the property. Then, since that function has access to both the encapsulating object and whatever object you are using for a name, you can create a simple temporary during the access which holds a reference to the encapsulating data and that has overloaded operator = and conversion. I chose operator ->* since it made the most sense.

So in the end, for the no overhead approach you can get access like:

code:
size_type const current_size = my_container->*size;
my_container->*size = 10; // resize to 10
Where size is just a dummy object stored at namespace scope, so you might even need to qualify it depending on context. This can be optimized to absolutely no overhead, requires no unnecessary data to be stored within the container, and can even be implemented entirely outside of the class definition at namespace scope. Of course, once you start using operator ->* instead of operator . you completely get rid of many of the use-cases for properties, so it becomes somewhat pointless. In the end, it's just not worth trying to make a library for properties in C++ as you really need language support, or at the very least, an overloadable . operator, and even then you still have the problem of namespace qualification.

That Turkey Story
Mar 30, 2003

I haven't tested it thoroughly, but I just put together a little macro for simple, safe enums if anyone wants it. You need boost installed to use it (header only). I only compiled with VC++ 9 but it should be portable unless I made a poopie, which is probably pretty likely. Right now, enums must be created at namespace scope, but with some effort, that can be changed.

To make a simple direction type that has north, south, east, and west constants and that default constructs to north, use:

code:
SAFE_ENUM( direction, (north)(south)(east)(west), north )
To make a simple direction type with optional initialized constants (note the additional parenthesis):

code:
SAFE_ENUM_WITH_VALUES( other_direction, ((north)) ((south)(6)) ((east)(15)) ((west)), north )
Construction and assignment can only be done via copies or by one of the above constants. There is also no implicit conversion to the enum's promoted type:

code:
// Assuming the above macro invocation...
direction a = direction::east,
          b = a,
          c = direction::default_value,
          d;
To access the value of a constant as a valid compile-time integral constant (useful for switch cases and metaprogramming):

code:
// Again, assuming the above macro invocation...
direction::value_of::north
To access the value of an instance of your enum at runtime, use:

code:
some_instance_of_your_enum.value();
To access the number of constants that were specified use:

code:
// Note, the value of the expression is not a valid direction itself.
// It is also a valid compile-time constant expression that can be used,
// for example, as an array size.
your_enum_type::amount_of_constants

That Turkey Story fucked around with this message at 01:59 on Jun 6, 2008

That Turkey Story
Mar 30, 2003

Paniolo posted:

What's the most elegant way for a class to expose a collection? At the very least, it needs add, remove, and enumeration faculties. Preferably it shouldn't be tied to any particular container type, either. I've run up against this pattern a few times and haven't found any particularly elegant solution.

Could you elaborate more on your situation? Before you decide to directly expose the container, make sure you can't just represent your functionality as higher-level operations that are internally implemented with a container. If the encapsulating class holds the container but all the manipulation is still done directly by users rather than indirectly through the class, then your type is likely just a low-level aggregate (which is okay depending on your situation, think std::pair or tuple types) or it is a container itself (which again is okay).

In the case of it being an aggregate, you can always just directly expose your object rather than making a redundant interface. If it's a container itself, the easiest way to make your type a compliant container without an is-a relationship is to privately inherit from the container you are using and then promote the interface elements to public with using. From your brief description, it sounds to me like you are in more of the latter case. If you feel as though your type is neither of these things, then try to tell more details about what you are doing.

That Turkey Story
Mar 30, 2003

floWenoL posted:

What do you guys think of this?

I just skimmed and it's not bad, but some stuff I do disagree with. I will probably make it sound worse than it is because I get really upset when people give foolish rationales, but really nothing in here is horrible.

quote:

All inheritance should be public. If you want to do private inheritance, you should be including an instance of the base class as a member instead.

In modern C++ there are lots of reasons to prefer private inheritance rather than datamembers in certain situations, some of which are techniques that simply only work with inheritance as opposed to composition. For one, private inheritance is useful for minimizing redundancy by inheriting from a base and pulling in desired names with a using declaration rather than having to tediously create I.E. forwarding functions (which can be error prone and less efficient if not done carefully, not to mention potentially less clear). Another example is helper bases that pull in functions that can be looked up via ADL (I.E. Boost.Operators). As well, using inheritance let's you take advantage of the empty base optimization, which as a side note, is exactly how Boost compressed pair works which is on this list's approved Boost libraries -- the only approved component of Boost on the list actually (so basically the guide doesn't want you to do private inheritance, but it's okay to use it indirectly, which stinks of Java's operator + logic to me).

quote:

Only very rarely is multiple implementation inheritance actually useful. We allow multiple inheritance only when at most one of the base classes has an implementation; all other base classes must be pure interface classes tagged with the Interface suffix.
This kind of goes back to what I was just talking about : particularly with private inheritance, multiple inheritance is common and often desired. It's true that multiple public inheritance is often a bad choice (though even then not always), but issues aren't really there with the modern uses of inheritance in C++. Again, Boost.Operators is a great example of this.

quote:

Use 0 for integers, 0.0 for reals, NULL for pointers, and '\0' for chars.
Back in C, NULL was much more of an option to consider since it could be defined as ((void*)0) since implicit conversion from void* to other pointer types is implicit in C (whereas void* to integral is not implicit in C or C++). In C++, however, NULL is pretty much limited to being defined strictly as 0 unless your compiler defines it in some way outside the realm of a normal #define and it provides little benefit over just using a raw 0 other than possibly to signal the context of a pointer in text or the chance that your compiler makes a special case for NULL (which most do not). As well, the only way you can [portably] use NULL is if you include certain standard library files which can be silly since 0 works just fine. If you really want to have a different name from 0 to use for null pointers, it's generally best to use a custom null-pointer object, which can let you make a more proper solution than a simple macro. NULL is more of a legacy from C that carried over as some people switched from C to C++.

quote:

Do not overload operators except in rare, special circumstances.

...

Cons: While operator overloading can make code more intuitive, it has several drawbacks:

* It can fool our intuition into thinking that expensive operations are cheap, built-in operations.
* It is much harder to find the call sites for overloaded operators. Searching for Equals() is much easier than searching for relevant invocations of ==.
* Some operators work on pointers too, making it easy to introduce bugs. Foo + 4 may do one thing, while &Foo + 4 does something totally different. The compiler does not complain for either of these, making this very hard to debug.

...

In general, do not overload operators. The assignment operator (operator=), in particular, is insidious and should be avoided. You can define functions like Equals() and CopyFrom() if you need them.

...

Some of the STL algorithms do require you to overload operator==, and you may do so in these cases, provided you document why.

This I disagree with entirely. First, I don't understand this talk about people expecting operations to be "cheap" (or even what the vague term of "cheap" means in the general sense). You expect the operation to do what it's supposed to do, and if there is a required complexity for a given domain at concept then it should be made known. Multiplying two matrices might be expensive, but it's still a multiplication operation. Concatenating strings with + is intuitive. Anyone who somehow thinks that using a named function called "multiply" does anything other that make you type more and make it harder for your object to be used in generic code is simply not used to a language with operator overloading and that's it. There is nothing special about operators that makes them any different from other operations other than syntax and this whole nonsensical idea of them having to be "cheap" is just silly.

As well, the comment "Foo + 4 may do one thing, while &Foo + 4 does something totally different" really just blows my mind in that I can't believe somebody genuinely thinks this is a good point. Are they trying to tell me that at some point in code somebody honestly accidentally wrote &Foo + 4 instead of Foo + 4, and after doing that, it somehow bit them in the rear end rather than being caught by the compiler? Are they also missing out on the point that what they are saying would be true of integers and the built-in operator +? Maybe they are suggesting that people should use a named "Add" operation for built-in arithmetic types as well, since that goes along with their argument?

Anyway, aside from that being an odd mistake to make to begin with and the fact that their "rationale" would apply to built-in operators, the expression given would likely never occur alone like that in a statement as it would be a part of a larger expression, since unless the + expression they showed has side-effects, you'd be losing any calculation being made anyway (and if the statement has no side effects, that typo would generally make no difference since there is no observable change either way, though you technically might get undefined behavior with the &Foo + 4 depending on context, but even I'll say that's a moot point and I'm a pretty pedantic language lawyer).

Anyway, getting back to what this means -- this "&Foo + 4" expression will be a sub-expression of something else, for instance, you'd store the result of the plus operation in another object or pass the result to a function. C++ is a statically-typed language, so unless the rest of the expression can take a pointer as an argument or an object of whatever the result type of the + is here, you'd get a compiler error so you'd know your mistake right away! I find it extremely hard to believe that anything like what they described has or ever will occur in any competent [or incompetent] person's code.

The fact is, using + makes perfect sense here for built-in arithmetic types, pointer types, and user-defined types. Barring contrived examples that don't even make sense, you have to really push to come up with sound reasons against them as opposed to being driven by blind, religious issues with operator overloading.

Finally, they claim that overloading the assignment operator is bad as well along with the equality operator. I find it extremely hard to believe that using "Equals" instead of "==" is more clear or less error prone. Again, all I see it accomplishing is making it harder to have your objects work in generic code and making it seem like operators are some special devices that are only for use with built-ins. Their excuse that it makes it easier to search for in code is also very weak considering all you're doing is weeding out built-in operations, whereas if all of your user-defined types use the same name (I.E. Equals) you have the same exact "problem" (which I can't even really see as being a problem to begin with).

All of their issues with operators are extremely contrived and sound like worries that people might have if they worked their whole life in another language and got an idea stuck in their head of what they personally think an operator should be as opposed to what operators really are, at least with respect to C++.

quote:

All parameters passed by reference must be labeled const.

This is C++. You can pass objects as references, that's how the language works. Using non-const references for parameters which modify an object as opposed to a pointer is the preferred C++ way of having functions which modify arguments as it removes the issue of making it unclear about the handling of null pointers and overall it simplifies syntax. If it is unclear that an argument to a function should take a non-const reference then that is generally a poorly named function and/or the person trying to use it doesn't understand it's purpose, which either way is a programmer error. The only time I see people have issues with this are when they come from a C background where references just didn't exist so they just got used to always passing out parameters by pointer. There is a better solution in C++ so use it and stop clinging to an out-dated C-style approach.

quote:

If you want to overload a function, consider qualifying the name with some information about the arguments, e.g., AppendString(), AppendInt() rather than just Append().

This is just silly. Again, they all correspond to the same logical operation and having to repeat the type of your expression just to call the function is redundant, not to mention annoying. You also just once again make it harder to use the function in generic code.

quote:

We do not allow default function parameters.
link

...

Cons: People often figure out how to use an API by looking at existing code that uses it. Default parameters are more difficult to maintain because copy-and-paste from previous code may not reveal all the parameters. Copy-and-pasting of code segments can cause major problems when the default arguments are not appropriate for the new code.

Decision: We require all arguments to be explicitly specified, to force programmers to consider the API and the values they are passing for each argument rather than silently accepting defaults they may not be aware of.
First, if you are trying to figure out an API by copying and pasting code then you need to stop. If you don't understand the semantics of a function then why on earth are you calling it? At the very least, read the comment for the function. If you are naively copying a function call, alarms should be going off in your head questioning the semantics whether there are defaults or not.

quote:

You should not use the unsigned integer types such as uint32_t, unless the quantity you are representing is really a bit pattern rather than a number. In particular, do not use unsigned types to say a number will never be negative. Instead, use assertions for this.
Holy moly. Goodnight.

Edit: I don't know why you're getting all upset about naming and indentation, etc. You can't really fault anyone for something that's nearly entirely a subjective choice.

That Turkey Story fucked around with this message at 08:30 on Jun 30, 2008

That Turkey Story
Mar 30, 2003

floWenoL posted:

Trap loving sprung!

Edit:
Actually kind of backfired because now I want to do a point-by-point rebuttal. :argh:

Let the games begin!

That Turkey Story
Mar 30, 2003

Zombywuf posted:

Also, exceptions, use them.

I agree, but the explanation given in their decision section makes sense (their "con" list for exceptions on the other hand is terrible). The overall rationale for their decision is that a lot of the code they already have written is not exception safe and their new code needs to correctly operate with old code. I can only imagine the type of rewrite it would take to make all of the previously written C++ code at google exception safe.

As for auto_ptr, I agree with you. In particular, there really isn't an alternative to auto_ptr for returning a dynamically allocated object and having the result be properly automatically managed. You simply cannot do this with scoped_ptr. When C++0x comes around this will be a different story, but at the moment, auto_ptr is the ideal standard solution.

Edit:

floWenoL posted:

Boy howdy it sure must be nice to live in your world where performance doesn't matter!

That was initially what I thought, Flowenol, but if you read their rationale, performance isn't even mentioned:

quote:

On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.

Given that Google's existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in in a new project. The conversion process would be slow and error-prone. We don't believe that the available alternatives to exceptions, such as error codes and assertions, introduce a significant burden.

Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. Because we'd like to use our open-source projects at Google and it's difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well. Things would probably be different if we had to do it all over again from scratch.

There is an exception to this rule (no pun intended) for Windows code.

The bolded sentence at least makes me happy :p

That Turkey Story fucked around with this message at 17:57 on Jun 30, 2008

That Turkey Story
Mar 30, 2003

floWenoL posted:

Just because it's the "preferred C++ way" doesn't mean it's the best way. Your argument as to the clarity of a function taking in a non-const reference equally applies to whether it can take in a NULL pointer argument.
Wait what do you mean? What I meant with respect to null pointers here is that you can't legally work with a "null reference" without doing something that implies undefined behavior. On the other hand, null pointers are perfectly fine and allowable in the language. Therefore, if your function takes a reference it's clear without even it having to be explicitly documented that a constructed object must be passed. On the other hand, with a pointer, that restriction is not implicitly there. I'm sure that this is evident in code which uses this convention simply by looking at the body of the functions that take such out parameters -- is one of the first lines "assert( some_out_parameter );" or a similar kind of check? If not, it probably should be. With references, issues like that simply don't exist.

Aside from that, it's the "preferred" c++ way not by authority, but because it's the exact use the tool was made for and because it does everything the pointer version does for this situation but has more fitting requirements. If I personally saw code that was passing a pointer rather than the object itself, all I would have is more questions. If all you wanted to do is make it more explicit where data was being modified, which is what it seems from your argument, rather than using pointers, why not make it convention to use something like boost::ref instead, or a similar basic reference wrapper. This way you can visually see at the call-site what's an out parameter without removing the strict requirements that a reference implies. You get the explicitness that you desire without the side-effect of making it look like you want to pass a pointer to an object rather than simply the object itself as an lvalue, and you make it so that you can't legally pass a null pointer as well. Would this not be the best of both worlds? You can still easily grep -- perhaps more easily than with a pointer-pass convention.

In other words, just use the convention "some_function( a, b, out( c ) );" where "out" is a function template that returns a reference wrapper. Now you are making your coders be explicit as well as abide by the limitations that references have and which pointers do not.

Going off on a tangent for a second, what do you think about non-const member functions? By this I mean that calling a const member function is the same syntax as calling a non-const one (which boils down to are you passing your object by const or non-const reference once you remove the syntactic sugar). Do you see this as a similar problem to what you're describing? In other words, if you are using a non-const member function should it be explicitly noted at the call site by using a pointer to call the function instead of the object directly, such as what you do when it is a more traditional style function argument? I'd imagine you'd say that always using a pointer here would be ridiculous -- is that just because it's more expected that member functions modify the data or because there is one place to look for all member functions or is there some other reason entirely?

floWenoL posted:

Leaving aside the brokenness of having assignment mean 'move' instead of 'copy' sometimes (C++ overloads so many things already, why not poach an operator for this? I suggest "x << y;")
Firstly because without rvalue references you simply can't make that syntax work with temporaries as you can't bind a temporary to a non-const reference. For instance, "string a; a << ( some_string + some_other_string);" would not compile without rvalue references unless you made the operator's second argument a const-reference, which would incorrectly pull in const lvalues (not to mention that you'd have to use a potentially incorrect const_cast). Then there is the fact that "x << y" already has an entirely different meaning for many common types (I.E. unsigned << unsigned is a left shift and so it would conflict with a move operation for unsigned).

Not only that, but it's extremely beneficial to have move and copy dispatched with the same syntax based on whether you are working with an rvalue or lvalue since it makes things such as the above string statements work with exactly the same written code whether a move operation is implemented or not. This is good because at a high level, a copy of a temporary here is the same logical operation as a move of a temporary since you can't access the temporary again to observe a potentially different sate. You can look at it as an implementation detail that pulls in an optimization based on type information known at compile-time.

This has huge benefits. In generic code, or simply in code that was written prior to a move constructor or move assignment operation being coded, this means that the program is able to instantly take advantage of move semantics once they become available without having to go back and change anything as it was written, since it will simply be picked up by overload resolution (as it logically should be). All you have to do is recompile and your code is suddenly faster with the same meaning. For instance, in modern C++ code, once standard C++ types such as strings adopt proper move support, users of those types instantly have faster code without having to change any of their written code at all, whereas if a separate operator were used for moves instead, you'd only be able to take advantage of move constructions and move-assignment operations if you explicitly went through and changed your own code to use this new "move" operator everywhere instead (not to mention that it would be error-prone to go back and do and you would likely miss some cases).

floWenoL posted:

honestly once you remove exception handling that removes maybe 80% of the need for smart pointers, and in the remaining cases auto_ptr is hardly an 'ideal' solution;
How does having no exception handling remove the need for smart pointers, even by "80%"? Maybe I'm weird, but I don't think I've ever used raw dynamic memory allocation without some kind of smart pointer in 4 or 5 years, and in many of those situations they are in code which can't throw exceptions at all anyway and where the code will likely never need exceptions in the future.

The idea of smart pointers is that the memory management is handled automatically so you don't have to manually keep track of ownership, and when all owners are gone, the memory is automatically deallocated. Without any exceptions at all, it's still extremely beneficial to use smart pointers since it makes it impossible to forget to deallocate your memory, especially if you are doing something like returning from a function in multiple places. No matter where you return, your memory is properly deallocated, whereas with explicit management you have to remember to deallocate at every path and you'll just get a silent memory leak if you happen to forget. This kind of memory leak goes away entirely with proper use of smart pointers.

It's true that RAII helps make exception safe code, but saying that 80% of the use of RAII goes away without exceptions is a pretty big generalization, and even if it did happen to be true for many domains, the remaining 20% is still an important 20% that you shouldn't overlook. Even if it were somehow 1%, that's enough to make it important.

floWenoL posted:

look at all the people that have tried to write replacements for it, even Alexandrescu!
That's only because there is no way to replace it in current C++, at least not in the ways one would like, but that will all change come 0x. For now, you just have to use auto_ptr or something similar.

That Turkey Story
Mar 30, 2003

ZorbaTHut posted:

I've seen a surprising number of bugs caused by people writing reasonable, sensible equations that break in unexpected horrifying ways when the types involved are unsigned. For example:

code:
string mystring = getdata();
for(int i = 0; i < mystring.size() - 1; i++) doThings(mystring[[b][/b]i]);
If mystring.size() is 0, it underflows and wraps around to 4.3 billion. Whee!

First, before going into anything at all, I'd recommend using iterators here which as a side-effect avoids the issue of sign entirely, and if for some reason you didn't do that, I'd still say say use string::size_type instead of int and just don't write an algorithm which relies on negative values. The reason is, if you write your loop correctly and your loop uses the proper size type, your code works for all strings. If, on the other hand, you use int, your loop variable will risk overflow on larger strings no matter how you write it. Your algorithm is not going to be correct, at least in the general sense, not including limits from context. In the end, I'd rather write the code properly and have it work for all cases as opposed to write it in a way that works only for some subset simply because of a fear and/or misunderstanding of working with unsigned types. I'd hope that in your example you're at the very least asserting that mystring.size() <= numeric_limits< int >::max().

Either way, your for loop example is hardly indicative of why you should never use unsigned types even if that code actually were able to handle all strings. If anything, at least just recommend defaulting to signed if you are unsure rather than disallowing unsigned types completely for arithmetic calculations, though even that I'd disagree with. Use the type that makes sense for the job. Period. Strictly disallowing unsigned types for arithmetic calculations is foolish and is going to push you to write code that is incorrect in very subtle ways. Programming can be tricky, but things like forcing people to use signed types only gives the illusion of making your code safer.

floWenoL posted:

You also have to take into account that using unsigned inhibits compiler optimizations and so that 'free' range boost may come at a performance cost.
Wait, what? If anything I can think of cases where unsigned operations can be optimized whereas signed operations cannot, not the other way around. Maybe you know something I don't, but either way, that doesn't change the fact that using an unsigned type may be correct whereas a signed type isn't (or vice versa). Pick your type based on what makes your code more correct.

That Turkey Story
Mar 30, 2003

crazypenguin posted:

Zorba provides a slightly contrived situation but a very good example of the kind of subtle and unintuitive bugs unsignedness can produce, and all you can do is rave about how the situation is slightly contrived?
You need to reread this conversation if that is what you pulled out. The problem is the signed version won't work for all strings whereas the unsigned version will. You're trading writing a proper algorithm for the ability to avoid an amateur mistake that could easily be picked up in testing. One is correct, the other is not.

crazypenguin posted:

The point is you can do perfectly normal things with signed integers because it's pretty easy to avoid +/- 2 billion (all you have to consider is whether 32 bits is big enough). You pretty much always have to start thinking about modular arithmetic with unsigned integers because it's drat hard to avoid 0.
What the heck are you people talking about? I'm no genius here, but avoiding going below 0 is not exactly rocket science. Maybe if someone were coming from a language without unsigned values I can see them being in the habit of not thinking about sign when jumping into C++, but you have to get over that. In C++ you are working with standard data-structures that deal with particular types and you are simply wrong if your algorithms that deal with them are using a mismatched type (unless you force certain constraints that are unnecessary for an algorithm written with the intended types). I don't understand how we can even be having this discussion.

floWenoL posted:

It's worth pointing out that using iterators here isn't correct either. For an empty string, begin() == end(), and so you'd be comparing one before the beginning of the array, which isn't necessarily valid (according to the standard). Not to mention the fact that if you used == instead of < (as is common with iterators) that would be an error, too.
Iterators are perfectly fine here. Of course you can't just swap out signed types for iterators and have the algorithm work the way you wrote it, just like you can't swap out signed values for unsigned and have it "work." You have to write the algorithm appropriately for the type. The difference is that when you write the code correctly with size_type or iterators, your code will work for all strings. If instead you write it with int as was done in that example, it simply won't work for all strings. It boggles my mind that I have to argue with computer scientists because they simply insist on using the incorrect type for their algorithm. It's not hard to simply write your algorithm to not rely on negative values. Again, here is your choice -- write your code correctly, or write it incorrectly just to avoid an amateur mistake that can easily be found in testing anyway.

At least Avenging Dentist and falafel are sane.

floWenoL posted:

It's not that iterators are entirely inappropriate, it's just that sometimes using indices is clearer; recommending "always use iterators" is the C++ equivalent to the (equally fallacious) mantra of "always use pointers".
It's not that you should always use iterators, just that it's a more generic and safer default than using random access, especially if you are doing so with the wrong type. Why? Firstly, it gets rid of this whole data-type issue which I honestly didn't even realize people had a problem with until this thread, and secondly, if you write your algorithm with iterators it's a much more generic framework for the algorithm that can easily be adapted to work with any range that has similar requirements. It's not any more difficult than working with indices at the high level, it's just different.

floWenoL posted:

If you have a vector and you know you won't have enough elements to run into size issues, using iterators is unnecessary, verbose, and in fact may introduce bugs due to the fact that you have to repeat the name of the container twice, which is exacerbated by the fact that said verbosity encourages copying-and-pasting iterator-using for loops as to avoid having to type out vector<blah blah>::const_iterator or "typename T::const_iterator" (don't forget the typename!) yet again.
Those errors you are talking about would be caught by the compiler before you even run the program, and I know you hate hearing it, but the STL, TR1, and boost have plenty of tools available that take away redundancy. We'd all love to have auto and decltype I know, but for now it's the best one can do.

Entheogen posted:

the reason to use signed integers for indecies of arrays is that you can set it to -1 to signify it not pointing to anything. Which sometimes you have to do. Otherwise what is the issue? both take up the same space.
:sigh: Maybe everyone really should just use java. I don't know how to explain this anymore clearly.

That Turkey Story fucked around with this message at 01:55 on Jul 4, 2008

That Turkey Story
Mar 30, 2003

Quit being a jerk floWenoL. Let's all just relax and have a nice discussion about how much C++ owns and how everyone is a terrible programmer except for me, you and AD.

That Turkey Story fucked around with this message at 02:27 on Jul 4, 2008

That Turkey Story
Mar 30, 2003

I find it hard to believe that using the proper datatype is over-engineering.

That Turkey Story
Mar 30, 2003

I usually just run it and then count aloud to myself.

That Turkey Story
Mar 30, 2003

I'm really good at counting.

That Turkey Story
Mar 30, 2003

Entheogen posted:

Also is there a way to get around using some confusing rear end referencing and dereferncing operators when dealing with iterators on containers that contain pointers themselves?

Adapt the iterator similar to how you make reverse iterators. Boost actually has an iterator adapter for exactly your situation.

Edit: Also use BOOST_FOREACH instead of that macro you posted! It's more portable and only evaluates its macro arguments once.

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

Ok, so I'm using private inheritance and promoting the methods I want to expose in the base class with using directives (did I get those technical terms correct?) but I'm a bit confused about one thing.

Say I want to expose only the const version of a method, how do I do that? This doesn't work: using base::operator[] const;, and using base::operator[]; exposes both the const and non-const methods.

Is there a common workaround for this or is my syntax wrong?

You can't. You need to make a wrapper function.

That Turkey Story
Mar 30, 2003

litghost posted:

I am sorry to say this completely wrong. C++ lets you do terrible terrible things sometimes:

No, your code is what's wrong. ref_bob's initialization has undefined behavior as does trying to access ref_bob2 after bob2 was deleted. There is no way to legally work with a null reference and it is safe for your compiler to rely on that fact. Null pointers on the other hand are perfectly fine and can legally be formed.

That Turkey Story
Mar 30, 2003

Vanadium posted:


Now, for something, completely different, what is up with enum foo { a = 1, b = 2 }; foo f = a; f |= b; not being legal? Am I supposed to roll my own "flags" class?

code:
foo& operator |=( foo& left, foo right )
{
  left = static_cast< foo >( left | right );
  return left;
}

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

When is it safe to use this idiom:

Vectors are always guaranteed to take up contiguous space, but after you resize the sequence, any pointer you had into the sequence before becomes invalid.

Edit: So in other words, yes, your second example is safe (though the first one isn't because the vector is empty).

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

Awesome. I thought I read somewhere that using that idiom to fill a vector was bad, but it was just on some crappy blog and I never really trusted it. And being that I almost never need to use that idiom, especially for filling up a vector, I never looked into it further. Thanks for the clarification :)

Also, I'm just curious if there's a better way to get a char** out of a std::vector<std::string> than just allocating a char** big enough to store the memory and filling it manually in a loop:

code:
void dress(std::vector<std::string>& pants)
{
	char** fruitloops = new char*[pants.size ()];
	for (size_t i = 0; i < pants.size (); ++i)
	{
		fruitloops[i] = new char[pants[i].size () + 1];
		strncpy (fruitloops[i], pants[i].c_str (), pants[i].size () + 1);
	}
	
	crappy_thrid_party_api (fruitloops, pants.size ());

	for (size_t i = 0; i < pants.size (); ++i)
	{
		delete [] fruitloops[i];
	}
	delete [] fruitloops;
}

First, watch out because that code isn't exception safe! If any of those new operations in the for loop throw bad_alloc, you are leaking memory. Make sure you handle and rethrow or use RAII. A simple start is to make fruitloops a std::vector instead of a char** (or perhaps better, a smart pointer like boost::scoped_array if you have boost).

Second, do you mean the API takes a char** or char const* const*? If it's just an API not using const correctly, as in it's taking const-unqualified data even though it's guaranteed to not modify the data, you can just make a std::vector or scoped_array of char const* and then const_cast when passing the data through the API.

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

Oops!
No boost :( Do you mean a std::vector<const char*>? I've got a std::vector<std::string>, so you mean populate a const char* version by iterating through the std::string version and calling std::string::c_str() on each element? I thought you couldn't do that because of how std::string::c_str() works.

...


I believe that the function in question will leave the char** alone, but I'm a bit confused about how you mean to construct your std::vector regardless.

In other words (untested):

code:
void dress( std::vector< std::string >& pants )
{ 
  std::vector< char const* > fruitloops( pants.size() );

  std::transform( pants.begin(), pants.end()
                , fruitloops.begin()
                , std::mem_fun_ref( &std::string::c_str )
                );
	
  crappy_thrid_party_api( const_cast< char** >( &fruitloops.front() )
                        , fruitloops.size()
                        );
}

That Turkey Story
Mar 30, 2003

Lexical Unit posted:

The pointer returned by c_str() is valid to use so long as the string remains unchanged, correct? I think I'm getting the hang of this... I hardly ever need to think about this because I'm usually not interfacing with C libraries.

Yeah.

That Turkey Story
Mar 30, 2003

Thug Bonnet posted:

Well then putting aside the initialization list it seems like just doing assignment in the constructor would be more readable?

As long as you have all trivial datamembers or if you aren't that concerned with efficiency, use whichever one you find easier to read. It won't matter.

prolecat posted:

I have to disagree when the optimization in question is something that's already very easy to do and just as readable (in my opinion). No sense being slow when there's really no reason.

If you are so concerned about optimization, all floWenoL's version needs out of a compiler is NRVO support (which most if not all modern compilers support), and in the case of a struct with non-trivial assignment, floWenoL's version will likely be more optimized whether NRVO is supported or not.

Edit: In the case of all trivial datamembers, I agree, the version with assignments done in the constructor is the simplest approach and likely just as efficient.

That Turkey Story fucked around with this message at 18:08 on Jul 31, 2008

That Turkey Story
Mar 30, 2003

I think unordered_map is used because the name stresses its properties more-so than implementation.

That Turkey Story
Mar 30, 2003

Holy poo poo you guys are retarded.

That Turkey Story
Mar 30, 2003

Hammerite posted:

In the file classes.h there is the following definition of a class, "button":

code:
class button {
    int positionx;
    int positiony;
    int sizex;
    int sizey;
    bool exists;
    public:
    HWND bhwnd;
    button (int,int,int,int,bool);
    void ensurebutton(int,int,int,int);
    void destroybutton();
};
...

Further down functions.cpp there are functions that include lines like the following:

code:
buttons[BUTTONX].destroybtn();
and

code:
buttons[BUTTONX].ensurebtn(0,0,0,0);

destroybtn != destroybutton

ensurebtn != ensurebutton

Also, why are you using a macro for BUTTONX? Just use a constant if you need a constant.

Adbot
ADBOT LOVES YOU

That Turkey Story
Mar 30, 2003

The Red Baron posted:

Until C++0x brings along template typedefs, the only thing I can think of is indirecting the whole thing through some dummy class that lets default parameters actually be used, like so:
code:
template <template <typename, typename> class MapAdapter>
struct foo
{
  typedef typename MapAdapter<std::string, std::string>::type map_type;
};

template <typename Key, typename Value>
struct std_map_adapter
{
  typedef std::map<Key, Value> type;
};

typedef foo<std_map_adapter> teh;
Am I missing an obvious solution? Any thoughts?

This method is what is used right now, even in the STL -- that's pretty much how allocator passing and some_allocator::rebind works (allocator instantiations basically double as their own wrapper by design). STL containers don't take template template parameters for their allocator argument even though they logically could. This was done both because template template support was not common in 1998 and because it would mean that your allocator template would require the exact same amount of arguments as was specified in the template parameter list. Both of these problems are solved by adding a level of indirection as in your example.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply