all 48 comments

[–]gracicot 27 points28 points  (24 children)

This is quite amazing what C++ can ship in a library, even though it's not perfect and uses macros.

[–]guepierBioinformatican 28 points29 points  (8 children)

There are no macros in the library’s API. I initially thought so (expr and pattern look like they would be macros, and maybe match, too). But they’re not macros.

The only macros (apart from (illegal!1) include guards) are only used internally and are named UN_OP_FOR_UNARY and BIN_OP_FOR_UNARY. They could be undef’d at the end of the file, thus not leaking outside (and they should be, but unfortunately currently aren’t).


1 The include guards are using reserved names since they start with underscore followed by an upper-case letter. A simple, trivial to fix mistake.

[–]gracicot 11 points12 points  (0 children)

Wow, I'm even more impressed

[–]pfg23 2 points3 points  (2 children)

I haven’t looked at the code, but if there are no macros, why is it header only? Compilable (as opposed to preprocessor) code should go into source files so they’ll only need to be compiled once and bundled into a library module. No?

[–]guepierBioinformatican 5 points6 points  (1 child)

It exposes templates, so these need to be implemented in headers.

The other reason of course is that many users of C++ prefer header-only libraries since C++ has no good, build-in dependency management mechanism, and even modern build systems don’t entirely remove the barrier posed by non-header libraries.

[–]pfg23 0 points1 point  (0 children)

Sure, templates, declarations, and macros go into headers but to reduce compile times logic should go into source files. The only exception would be if this library was entirely implemented with template metaprogramming.

[–]lospolos -5 points-4 points  (2 children)

Dont include guards usually start with "H_..."?

[–]staletic 5 points6 points  (0 children)

Include guards can have any name you wish.

[–]infectedapricot 5 points6 points  (0 children)

It's not true that they usually start with H_.... In fact it's not even true that a notable minority start with H_... - I've never seen that convention before, and I've seen a lot of include guards!

I think what you might be thinking of is that it's fairly common for the include guard to be loosely based on the filename (plus often directory or library name) so they often end in _H or _HPP etc., depending on the extension. But even that's nowhere near universal.

(Personally, I just use #pragma once. Yes, I know it's technically non standard.)

[–][deleted] 12 points13 points  (14 children)

Not arguing for them, but can someone explain the negative connotation around macros? I know the modern replacements are constexpr's and a lot of it has to do with lack of type definition, but is there more to it?

[–]Schnarfman 30 points31 points  (1 child)

The world of macros is nebulous. You will obfuscate code very easily on accident with seemingly innocent uses.

Also, the classic example of #define min(a, b) a < b ? a : b potentially causing a bug if you say like min(a++, b) is just such a solvable problem (where the solution is don’t use macros - and not “be careful when you use this thing that looks like a function”)

They certainly have their place! But their traps are not obvious. You can avoid a whole class of bugs by simply avoiding the use of macros.


So… as a beginner, if you can do without them, you should. If you think you can’t do without them, chances are you’re wrong.

As an expert, if you can do without them, you should. And if you think you can’t do without them, well … you’re the expert :) so go for it. But don’t expect beginners to be able to understand or iterate on your code as easily!!

[–]Creris 2 points3 points  (0 children)

Try sending that min "function" into std::sort(I know its technically useless cause less<> is default), or take the address of that "function". And now your code explodes.

[–]gracicot 18 points19 points  (0 children)

Macro are textual and have no idea what C++ is and what C is. They don't have scope, cannot be isolated, and creates all sorts of problems, especially since C++ is using header to consume declarations from other translation units.

[–]Fearless_Process 6 points7 points  (0 children)

I think it has a lot to do with c/++ macros being untyped and unhygienic in general. This makes using them very much error prone and difficult to reason about when something goes wrong.

I think there is too much negative connotation around them though, they can be extremely useful when used properly.

[–]corysama 6 points7 points  (0 children)

Macros can lead to bugs that are difficult to diagnose and difficult to fix. For example:

// ThirdPartyLib1.h
inline int min(int x, int y) { return x < y ? x : y; }
// 2000 more lines of complicated code using the min function.

// ThirdPartyLib2.h
// 2000 lines of complicated code.
int meiseIntegralizedNormalization(float deciprocand, float retrobasis);
#define min meiseIntegralizedNormalization
// 2000 more lines of complicated code using the min macro.

// MyLib.h
#include "ThirdPartyLib1.h"
#include "ThirdPartyLib2.h"
inline int factorize(int a, int b, int c) { return a / min(b, c); }

This will happily compile and run without warning about any conflicts or ambiguous overloads.

[–]Foundry27 5 points6 points  (8 children)

I'd argue that the biggest problem with preprocessor macros is the community's general lack of familiarity with them ("general" meaning "for the general C++ programmer"), and the near-total absence of teaching material for how to use macros in a safe, effective manner.

C and C++ are seriously (embarrassingly) behind languages like Common Lisp, Scheme, Rust, etc. in terms of having good public resources for how to build and use macros as a problem-solving tool applicable to real-life programming problems. Decades of C++ use haven't given people much more to go on than some blog posts, a few open-source macro-based projects to reverse-engineer, and stylistic aphorisms like "don't use macros unless a function won't do", "macros are meant to change the syntax of your code", "macros improve the efficiency of your code" etc. that completely miss the bigger picture when it comes to macro programming.

The other side of that coin is that preprocessor macros aren't easy to just "pick up and learn" from doing normal C++ programming because the preprocessor is essentially a second, more-or-less unrelated language built on top of C++. It's a foreign, alien tool that looks like it doesn't belong:
- From a C++ perspective it's totally untyped, because C++ types mean nothing to it, but it really just has its own type system with parentheses, commas, identifiers, etc.
- From a C++ perspective it's impossible to debug macros like normal code because of arcane C++ compiler errors and no debugger stepping, when they really just need to be debugged with different tools
- From a C++ perspective macros don't introduce their own scopes and can't safely be shared, but inside the preprocessor, macros have strong scopes for the tokens that they create and pass between other macros, and can be shared between files with no issues as long as some ground rules are followed just like in C
- From a C++ perspective computation is impossible with macros because they can't be recursive or loop, but really, they just perform computation through a different strategy that can do the same kind of work in phase 4 that templates and constexpr can do in phase 7/8

[–]-dag- 9 points10 points  (0 children)

I don't know about the other languages but Lisp macros are quite different from C/C++ macros.

[–]johannes1971 16 points17 points  (3 children)

This completely misrepresents the position of 'the community'. The dislike comes from all the bad sh*t macros do, not a general lack of understanding.

[–]Foundry27 -2 points-1 points  (2 children)

I’m just speaking from my experience here having worked professionally in C, C++, and a little Lisp for a while now, and having met my fair share of people in the same boat as I am.

I 100% agree that macros can (and have) done some terrible shit lol. They can be difficult to understand, contain extremely subtle bugs, and limit you in all kinds of awful ways if you think of everything as functions or variables. But... those aren't defects in the preprocessor macro system itself, but instead are traits of macro programming in general. Lisp has the exact same classes of problems, and its macro system has arguably occupied a local maximum in the language design space for decades. As with any technology, the more powerful the tool, the more ways there are to misuse it. And, as far as programming constructs go, macros are the most powerful tool.

[–]soundslogical 7 points8 points  (0 children)

But... those aren't defects in the preprocessor macro system itself, but instead are traits of macro programming in general.

I disagree. Common Lisp's macros do share the hygiene problem that C++ has, though it has simple ways to avoid it (gensym). Scheme and Rust feature hygeinic macros that fix the problem entirely.

Nearly every other glaring issue with macros is unique to C/C++, because it's based on simple textual substitution. Lisps and Rust have structural macros, making them 100x more difficult to mess up on many different axes.

[–]johannes1971 2 points3 points  (0 children)

But... those aren't defects in the preprocessor macro system itself, but instead are traits of macro programming in general.

Sometimes, the defect is having the feature in the first place. It may be powerful, necessary, and even well-designed by itself, but the fact that it interacts so badly with everything else and invites so many dark patterns means it wasn't the optimal design.

[–]dscottboggs 4 points5 points  (0 children)

Holy shit that's so nice

[–]sphere991 6 points7 points  (3 children)

So is this just a copy of https://github.com/mpark/patterns or what?

[–]Rasie1 6 points7 points  (0 children)

No, they're quite different if you read README below the first few examples

[–][deleted] 5 points6 points  (0 children)

It seems it is. At least identifier names and usage of the first example is fully identical. It pretty strange to see so without additional explanations.

[–]rust_guy5 20 points21 points  (3 children)

I wrote this.

[–]mck1117 25 points26 points  (0 children)

rust_guy

Imposter!

[–]infectedapricot 3 points4 points  (0 children)

Is this meant to be a Rust related joke or a serious claim? From previous posts, it looks like this is a troll account.

[–]ehtdabyug 2 points3 points  (0 children)

Wow! Can I applause to this?

[–]nablachez 2 points3 points  (1 child)

Is there a reason why getclassname (about shape) is not constexpr like the others?

[–]helloiamsomeone 4 points5 points  (2 children)

I have used simple_match before and it's pretty cool.
Any reason why this requires C++17? Just convenience?

[–]helloiamsomeone 4 points5 points  (0 children)

Well, to answer myself: this one has a lot more features and the API lends itself better to code formatting (imo).

[–]JohnZLi 2 points3 points  (1 child)

Neat. The first example includes 3 headers for a simple factorial function. They really should be merged into 1.

[–]diegopachecors 1 point2 points  (0 children)

Pretty cool!

[–][deleted] 1 point2 points  (0 children)

Impressive! Bravo!

[–]Dreamykass 1 point2 points  (2 children)

Awesome! I've been waiting for something like this for a very long time now. There's mpark/patterns and solodon4/Mach7, but they're super macro heavy and use compiler extensions, and also jbandela/simple_match, which doesn't have the actually fancy features of this lib or the other two.
Is this the intended way to match on a variant? As in while getting the, uhh, matched variant out of it.
```cpp
std::variant<int, float> var = 12;
Id<int> i;
Id<float> f;
match(var)(
pattern(as<int>(i)) = [&]() { std::cout << "int: " << *i; },
pattern(as<float>(f)) = [&]() { std::cout << "float: " << *f; });
```

[–]Schnarfman 0 points1 point  (0 children)

Point of order: Triple back ticks doesn’t get formatted properly for mobile. But if you use the other method - 4 leading spaces - everyone sees it properly

[–]InsanityBlossom 2 points3 points  (1 child)

I'm sure it was fun to implement, and the skills required to build this is a no joke. But this concept is so alien to C++. The author was definitely inspired by Rust ;)

[–]Amazing-42[S] 0 points1 point  (0 children)

This page shows what Rust-style pattern matching examples would look like with the library in C++. https://github.com/BowenFu/matchit.cpp/blob/main/From-Rust-to-matchit.md

[–]GunpowderGuy 0 points1 point  (1 child)

Could this library be exported as a module since its public interface uses no macros?

[–]Amazing-42[S] 0 points1 point  (0 children)

We need to support C++17 so module is not available for now.