Click here to Skip to main content
15,879,613 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
I came across a problem that I was unable to solve, at least to my satisfaction...

The best solution I've came up with so far involves using a macro. (I know...)

IDE: Visual Studio 2012

C++
void main()
{
    std::vector<int>; numbers;
    numbers.push_back(100);
    numbers.push_back(200);
    numbers.push_back(300);

    // I'm not typing all of this every single time...
    numbers.erase(std::remove(numbers.begin(), numbers.end(), 200), numbers.end());

    // Better, but still, no.. (Having to supply the type really brings it down.)
    Vector::Erase<int>(numbers, 200); // See below for code

    // Nice.
    VECTOR_REMOVE(numbers, 200); // See below for code

    // I wish... :(
    numbers.Remove(200); // Doesn't really exist...   

    // I also tried making my own list type which used vector internally.
    // That way I could create the member method as shown above.
    // However, it had issues, and was dropped in favor of the other methods.
    // It was also the most work, since I had to re\create all the functionality.
    // In it's defense, the resulting interface was really nice. (Think C# List<T>)

    numbers.clear();
}

namespace Vector
{
    // Erase
    template <typename T>;
    void Erase(std::vector<T>& source, T item)
    {
        source.erase(std::remove(source.begin(), source.end(), item), source.end());
    }
}

#define VECTOR_REMOVE(vector, value) vector.erase(std::remove(vector.begin(), vector.end(), value), vector.end());


This problem extends far beyond this one example, thankfully, Boost has saved me from much of it, however, many problems still exist.

The real question of this topic:

Is a macro okay in the above scenario, if not, can someone point me towards a better solution that works, and uses the same or less amount of code as my current macro solution?

What about macros like this?

C++
#define FOR(var, iterations) for (int var=0; var < iterations; ++var)

FOR(i, 10)
 std::cout << "Hello World!" << std::endl;


I'm mainly using macros where snippets would probably work, however, macros produce less code 'visually' which makes for cleaner looking solutions, in my opinion.

Thoughts? Evil? Okay? Meh?

---

Note: I've searched Google, and most macro discussions mention logging\header guards, and math you shouldn't be doing with macros, none of the topics that I found addressed these types of solutions, and whether they were acceptable or not.

I'm actually undecided, that's why I'm asking for your advice on the matter, and perhaps, you guys can supply me with some other options that I've overlooked too. :D
Posted

First:
// Better, but still, no.. (Having to supply the type really brings it down.)
Vector::Erase<int>(numbers, 200); // See below for code
Vector::Erase(numbers, 200); // <-- This works well enough


I've got no problems with macros, but I would proably prefix something like FOR with something I can reasonably expect to be unique, like JDERT_FOR or something ...

Best regards
Espen Harlinn
 
Share this answer
 
Comments
[no name] 2-Dec-12 9:30am    
Thanks for the response. :D

With the first version, it can't always deduce the type under VS2012, which can be a problem with longer types(vector of vector of type, vector of map of key value pair).

As for the unique name for the "FOR" macro, could I perhaps place it in a namespace, or will it still pollute the global namespace, in either case, it's nice to know it's not a total abomination in the eyes of other programmers. :P

Thanks again. :D
Stefan_Lang 3-Dec-12 9:16am    
If you're having problems searching a *value* of a very complex type, then this raises the question why you search by value to start with. Comparing an entire instance of a complex object might be expensive. Wouldn't it be more performant and elegant to instead search by some kind of identifier, or just the key in the example of key/value pairs? Why would you want to compare the key and the value?

As for namespaces: no they do not affect macros. The only way to restrict the space that a macro name pollutes, is using it locally within a cpp unit. You can also define and undefine a macro within the same header to prevent it from interfering with anything coming after that header, but that wouldn't help for your purposes.
Espen Harlinn 2-Dec-12 9:35am    
Macros got a bad name when developers tried to emulate the work done with the NIH Class Library:
http://www.softwarepreservation.org/projects/c_plus_plus/library/nihcl

This was before templates were added to the language, so they used macros to implment typesafe containers, which was a neat trick if you got everything right, and pretty hard to debug if you didn't.
[no name] 2-Dec-12 9:45am    
Yeah, there is a lot of hate, so it makes it hard to tell when you should or shouldn't use them. :)

(Same goes for a lot of programming practices, really.)

I think my use of macros is going to be fine, since I'm basically doing find\replace style operations with them, as opposed to trying to do the work of templates, etc,.

Even still, it's hard not to get carried away with them.

#define STR(source) std::to_string(source)

That would be a time saver, but, has to be macro abuse. ;)
Espen Harlinn 2-Dec-12 9:52am    
Have a look at The Boost Library Preprocessor Subset for C/C++:
http://www.boost.org/doc/libs/1_52_0/libs/preprocessor/doc/index.html

Macros are useful, but it's usually a good idea to think about your coworkers - will they understand your code? Or yourself in a couple of years ;)
While there are exceptions in favor of using macros, the examples you've given are a definitive NO-GO! Here are the reasons:

1. Macros pollute the global namespace: if you intend to use them locally (i. e. within just one cpp unit), then neither the readability nor the safed time for typing are a good argument, as the locality pretty much nixes both. If not used locally, the macro names will be visible within the entire solution and may potentially conflict with other code, including third party libraries!

Example: I've once spent a full week due to such an error caused by a max macro (defined in one of the windows headers, no less!), because it blew up Microsofts implementation of std::vararray (which also has a function called max)
2. Macros are not typesafe, and generally hard to debug.

3. The examples you've given can easily be implemented as global functions, or template functions. Those would be typesafe and could be included in a namespace to avoid conflicts.

4. It's way too easy to introduce mistakes in a macro that don't immediately show up and instead will plague future programmers. Since the result of such mistakes will usually be impossible to trace back to it's cause, they will instead require a full-blown-code analysis.

5. Your reasoning is doubtful: personally I prefer the original explicit version, as I understand them and know precisely what they do. Even if I don't, the typesafety of their definition will liekly result in a compiler error when I don't use them correctly. The Macros however are unfamiliar - I have to trust they do what *I* believe what they do, and if they don't, then anything can happen. To avoid that, I have to look up the definitions, and understand them. And even then, if I make an error, the resulting error messages will likely not show me the actual cause! Nor give a hint how to fix it!

Conclusion: If you do a project all on your own, with no-one else involved, and you do not intend to ship it as a library for others, then do whatever you feel most comfortable with. If not, then save yourself and everyone else a whole lot of pain and avoid those unneccesary macros.

P.S.:
Think about the following question:
Can you write error-free code without access to a compiler?

If yes, feel free to use macros whenever you like.
If no, then any macro you write is bound to cause hard to fix errors.

Because writing macros is like switching off compiler syntax analysis.
 
Share this answer
 
v2
Comments
[no name] 3-Dec-12 12:08pm    
Thanks for the well thought out answer. :)

I do have some comments\questions.

1. Namespace pollution: I agree this is a problem.

Would changing the names help any? (ie, MACRO_STD_FOR(1, 100))

Then we have def\undef, which boost uses on it's macros, couldn't something like that be used to hide them away?

2. Type safety\debugging: Can't argue here.

I've already hit a few issues, though, keep in mind, I was intentionally seeing how far I could push them, what they could do, and couldn't do, etc,.

3. Global functions: I'm slightly confused.

Wasn't my first version exactly this? It was a global function, tucked away in a namespace. (ie, Vector::Remove) ???

At any rate, the main issue I have with this version, is that the IDE isn't deducing the type of the T parameter in many cases, forcing me to type it out.

It may not seem like a huge deal, but, these sorts of things pile up.

I also dislike the inconsistency of it visually, I'd have to always type out the type parameter to avoid this regardless of whether the IDE picks it up or not.

4. Impossible\Code Analysis:

I don't 100% agree here, though I do see your point, and the potential for problems with macros.

Here's the crux though, I test my code, and I'm not talking about the macro, I mean in general, so, if I make a change to my code I compile it, and test it, and if something went wonky out of nowhere, I can safely assume that I've isolated the problem to my last changes.

I realize that may not be ideal on HUGE code bases with multiple programmers, etc, but still, I'm fairly aware of my changes, what worked, and what didn't, so, even then I can reasonably narrow down the source of the issue.

5. Reasoning\Etc:

What's doubtful about my reasoning?

In some cases I was able to save over 50 characters. Typing 20-50 less characters per call is huge savings, you're obviously speaking from the: I get paid by the hour perspective. ;)

I assume by original, you mean the actual STL code, ie, the long version, correct? If so, then you should have NO issues with trusting my macro, seeing as they produce the same output.

The IDE literally performs a find replace operation, replacing my macro with the code you claim to trust.

I can't argue about it losing type safety, however, even the quickest of glances at the macro reveal how it works, and what it does.

If you trust the STL code, then you have to trust a macro based directly on it.

It's like saying you don't trust this code.

#define HELLO_WORLD() std::cout << "Hello World!" << std::endl;

I assume you don't use logging macros, or header guards, or BOOST_FOREACH, etc, etc,. ??

Look, I'm just saying, at some point, you have to admit you're afraid of the code boogeyman if simple macros like mine worry you that much.

Again, I agree, they do have issues, generally speaking about macros, however, mine are very simple, and quite clear in intent. (And honestly, why are using the macro, if you don't understand it.)

Sure, I use the STL without fully understanding what happens under the hood, however, I know what the function does, and in the same light, I know what my macro does. (I'm saying, you shouldn't be calling random functions you don't understand.)

So really, I'm failing to see the problem in this regard.

----

Anyways, I do appreciate your answer, and the time you spent formulating it, and don't take my harsher comments too personally, they're not intended that way, I just think you're being a bit over dramatic about some things.

To answer your last questions:

1. Yes, this is a personal project.

2. Yes, I write 'mostly' bug free code all the time, and as mentioned, I test thoroughly, and you'll rarely find a try\catch block in my code. (I simply think about every possibility, and account for it, occasionally, some slip through, but, meh.)
Stefan_Lang 3-Dec-12 13:11pm    
1. Yes, undef helps, especially if you have no #include between the def and undef. And yes, using longer, more unique identifiers for macros definitely helps, but it counters the benefit of less typing effort ;-)

3. Yes, your first version was indeed ok, I was just commenting on the macrofication step. The example should work without the type specifier, so I didn't understand your reason for using a macro. I've read your comment about type resolution only later. I agree, that is annoying.

4. I've been bitten too many times by these kind of bugs. Probably, with the intelligence of current tools and the verbosity of error messages it is now less of a problem than 10 or 20 years ago. But I rarely see the benefit of using a macro outweigh the risk of introducing such bugs.

Yes, if you change some things, and use version control, and you see odd things going on, the effort of locating the cause is restricted to just the most recent changes. However, half the time I encountered such problems they were hidden away in some third party header that got included some 5-8 steps down the dependency path - and that's a lot of gruelling work just to pinpoint! Note that when I say the problems were hidden in that header, the header itself was in fact fine - it was just some macro introduced months ago (or by some other third party library) that caused that header to explode, e. g. because it got included in a different order than before.

The problem I pointed out under item 1. above is a perfect example: MS shipped both the incriminating windows header defining the max macro and the implementation of std::valarray, without even noticing the problem! It was only when I tried to create a windows project that uses valarray when everything came tumbling down! Just because the order of includes didn't match what MS expected (or tested).

Yes I didn't have a lot of code to analyze, but it never occurred to me (until much later) that the problem wasn't in my code at all. So I still lost a lot of time figuring out what happened.

5. saving on your typing effort is not a good reason to start with: modern editors with auto-complete safe you most of the work; C++ 11 lets you use the keyword auto to safe you typing out a result type; plugins like VisualAssist, and Power Tools help greatly in your work. But, most importantly: the time you spend typing code is neglectable compared to the time you spend testing, debugging, and fixing your code! You should always strive to minimize that effort first, and I've found macros tend to increase code analyzing and debugging times - often by a considerable amount.

As for your Hello World define: I can easily think of a use that will even compile, but do some nasty things at runtime. At the very least you should enclose your code in braces: that will avoid a whole lot of problems (and cause your compiler to cough more often if that macro isn't used as you intended).

As for logging, I'm using a class. And in the projects I work in, we're currently not using boost, but I'd trust the macros defined there, as they're very well documented (and yes, I've read that documentation!), and they are used for very good reasons.

I do use include guards. And I use conditional compilation for debug code and OS-dependend stuff. But that is about the extent that I use preprocessor symbols for. It may be radical, but less typing is not a sufficient argument for me, nor have I found another argument to use macros in the projects I've done. Instead I use constants, typedefs, template functions and template classes. And if the latter get too complex to use, I introduce helper functions that do away with most of the tricky stuff.

That said, my current project has several million lines of code, and >90% of that code wasn't written by members of the current team, so I'm pretty happy that macros are generally not used. I suppose in a small project, especially one you do by yourself, you'll have different priorities.

Finally I'd lik
[no name] 3-Dec-12 13:48pm    
It seems it cut you off at the end.

Regardless, as stated, I mostly(99%) agreed with you to begin with, but, I appreciate the clarifications, and additional answers, etc,.

As for the Hello World example, I'm sure you could make it blow up, but, you'd have to misuse it in order for that happen.

If you can show me an example of that macro being used as intended.

Example:

int main()
{
HELLO_WORLD();
}

And having it blow up, I'd be quite surprised.

Sabotaging code is easy, I can make plenty of valid C++ code blow up, that doesn't mean we should remove the offending features from the language, or not use them, etc,.

std::vector<int> test;
test.push_back(100);

for (int i=0; i < 2; ++i)
test.at(i);

Output = KABOOM!!!

Is that a failure of 'for' or 'std::vector' perhaps we should stop using both of them just to be safe. :P

Seriously though, 'for' doesn't know any better, but, 'std::vector' should have caught this, tsk, tsk,.

---

We've also failed to acknowledge some benefits of macros.

For example, there is no overhead from calls, since the code generated by a macro happens in place. (ie, VECTOR_REMOVE has no call overhead, Vector::Remove does.)

That could really make a difference in some cases. (Large loops, complex code, etc,.)

Also, it removes the need to write out templates, creating Vector::Remove took more code than VECTOR_REMOVE. (This can really add up, depending on complexity of the function you are macro-izing.)

Lastly, you can create code of nearly any complexity, you can make a "unit test" macro that can create code based on parameters for testing purposes. (I've tried this, it works.)

No other C++ method I'm aware of can do this, and it's very useful for testing.
Stefan_Lang 4-Dec-12 5:37am    
The cut-off part was just me pointing out that I appreciate the discussion, as questions about the use of macros too often turn religious (i. e. flame war).

In any case, my argumentation was mainly from the experience of large scale projects, and you don't need to take everything all that seriously if you're doing just a small, personal project.

As for using macros, my point was about the chance that someone (other than you) could be using it in a way that you *didn't* intend. Typesafe functions can be defined in a way to blow up at compile time if used in the wrong way, but macros may either blow up at runtime, or generate compiler errors that are hard to locate. Neither is a real issue in personal projects though.

Your point regarding overhead of function calls should consider two things:
1. can the call be inlined?
2. even if it cannot be inlined, is the performance overhead actually an issue? Usually 90% or more of your code is not relevant regarding overall performance, so it doesn't matter if you have some avoidable extra function calls. You won't notice that your code takes 0.0003s longer to respond. Function calls don't really take much time unless you pass complex objects by value, so the usual way to avoid overhead is pass those by (const) reference instead.

I understand and agree to your unit test argument: use cases and test cases are often pretty much the same, so it makes sense to use macros for both if that speeds up your code generation. Same for alternate scenarios. That is not actually complex code to begin with, so the chance of introducing unwanted issues is much smaller.
[no name] 5-Dec-12 12:09pm    
Thanks, I enjoyed the discussion myself.

I like to understand how things work, and why, and this stretches beyond programming concepts, into the choices made by programmers as well as in general aspects of life itself. :)

I absolutely loathe people who do it by the book, simply because the book told them to. (Even if it's correct, it's a bad way to operate.)

This is how we end up with so many incompetent people in so many fields. (Amongst other things.)

Sometimes you have to throw the book out, and you can't do that if you don't understand the underlying issues.

That's why I refuse to accept such simple answers as: Don't do that!

Why? When?

You can't formulate the rules outside of known areas of experience unless you understand these concepts.

If I were to tell you killing is wrong, absolutely, and then you find yourself in a position where you're forced to defend yourself, or your family, etc, that reasoning no longer holds up to the reality of the situation.

It becomes a gray area, where you're forced to think for yourself, and I'd much rather be armed with real knowledge than fortune cookie wisdom dished out wholesale by some dude I've never met in my life.

Anyways, after much toiling, and trial and error, the best solution I could come up with was a mixture of 1) and 4) from my original posting.

I have the external Vector:: functions in case I need to work with a std::vector directly, and I've also recreated my List class using those methods internally.

Whatever issue I originally had with method 4) was solved during the rewrite, it now works as expected.

It's a bit of overhead, and I'd prefer to NOT do it this way, but, this provides me the cleanest interface for working with std::vector.

Examples:

List<int> list;
list.Add(100);
list.Add(200);
list.Remove(200);
list.ForEach(...)
list.Where(...)
list.Clear();

Etc, etc,.

It's design should be fairly efficient since I'm really just hiding some details of working with std::vector, it may be slightly less performant, but, I doubt it's enough to even matter. (I'll profile it with Shiny, to be sure.)
Why don't you inherit vector and implement Remove function(that you so want). That would be one way of going about it.
 
Share this answer
 
Comments
Stefan_Lang 5-Dec-12 4:34am    
That is certainly a possibility, but certain operators (e. g. essignment) can not be inherited, and would need to be re-implemented. That's a whole lot of work for just one function.
Aswin Waiba 5-Dec-12 4:41am    
You could always inherit like
class ClassName: public vector{
public:
using vector:operator&();
//and so on
};

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900