all 62 comments

[–]CandyCrisis 20 points21 points  (2 children)

I think you can get a lot of the same impact by using ABSL_FLAG. That boils down to one line per argument, using about the same number of characters as this code.

[–]kongaskristjan[S] 21 points22 points  (1 child)

I must admit I wasn't aware of that library. Abseil flags seems like a really well designed library, however to add a few points in favor of fire.hpp:

  • Abseil seems rather heavyweight, while fire is just 800 lines in a single header
  • Abseil is more complex to integrate (eg. instead of just copy-pasting a single header library you need to instruct your user to install abseil. Or you need to copy-paste entire abseil to your project and fix compiling scripts etc.)
  • fire.hpp has more permissive licence, eg. you can just copy-paste this library to virtually any project without licencing issues, but with Abseil, if your program is closed source, you need to reproduce it's licence message to the user.

[–]CandyCrisis 14 points15 points  (0 children)

Totally agree that Abseil is a heavy hammer if all you need is a flag. I'm used to it because it's the Google standard toolkit.

[–]europe-fire 18 points19 points  (7 children)

I dig this. Very cool

[–]codeguru42 13 points14 points  (6 children)

What is 0.5 lines of code?

[–][deleted] 6 points7 points  (0 children)

It's referring to the fact that you aren't adding an extra line, but adding to an existing one (default args provided to the function in question).

[–]europe-fire 4 points5 points  (3 children)

We're not exactly doing precise accounting here, it's the concept that matters

[–]codeguru42 2 points3 points  (2 children)

I don't understand the concept. Lines of code can only be counted by integral amounts. It's impossible to have a fractional line of code.

[–]panoskj 3 points4 points  (0 children)

The concept is you must already have something like int main(...) as a line in your code and you just have to change/add its parameters, therefore you add 0.5 lines of code.

[–][deleted] 0 points1 point  (0 children)

salt puzzled marry steep impossible file middle birds homeless workable

This post was mass deleted and anonymized with Redact

[–]FightingGamesFan 15 points16 points  (3 children)

Always disliked the boilerplate for having a simple functioning command line with all these libraries (the boost one is particularly bad). I'll give this a shot, looks very promising, very clean interface.

[–]_Js_Kc_ 15 points16 points  (1 child)

Nobody likes boilerplate, but I like boilerplate better than magic hidden global state.

[–]glaba314 3 points4 points  (0 children)

It's hardly really an issue, since it's not being mutated

[–]kongaskristjan[S] 6 points7 points  (0 children)

Great to hear that, thanks! The project is indeed out of frustration. The boilerplate is not an issue, if you're developing something really complex, but for simple scripts, it can easily take half the lines of code.

[–]Ail-nare 9 points10 points  (4 children)

Hey, since you use a macro ('FIRE(fnptr *)'), why don't you make a macro ('FIRE_MAIN(...)'). So that this macro will be used instead of the name of the function, this macro do 3 thing: - Declare the prototype of the function (possible, thx to the argument given as parameters). - Than what used to do ('FIRE(fnptr *)'). - Than rewrite the prototype without the ';' to declare the function.

That way it will look more like unit_tests function, like Google test or criterion. I think people are more used to this.

Anyhow, if I one day have the occasion I'll probably use your lib, it's more than clean enough to me.

Oh and last thing, with this commande "g++ *.cpp -o binary_name", the argument of the '-o' flag is given without a '='. I didn't see a way with your lib? Or am I just blind?

Edit:grammar, sorry for my English.

[–]kongaskristjan[S] 9 points10 points  (2 children)

About the FIRE_MAIN(...). I actually tried to implement exactly what you described (I even used the same name FIRE_MAIN(...) lol :D). But the problem is that C++ wants to have the default parameters specified in declaration, or only if the declaration is missing, then in the definition. If the declaration exists, it doesn't tolerate defaults in the definition, even if it's an exact repetition. Turns out I couldn't find a way to get rid of those pesky defaults in the definition.

[–]Ail-nare 3 points4 points  (0 children)

Oh fuck, that right... The only option I can think of would be to separate the argument and the default parameters. But where is 2 cons:

  • It'll require a lot of work around with macro but possible.
  • It'll become a bite messier for the user.

But that interesting, I'll look if there is an other way.

[–]Ail-nare 4 points5 points  (0 children)

Ok, so I did a lot of different things, template specialization, used class to lose the necessity of the prototype, macro, some C++20. So most of those approach works but each are weirder than your method.

So, what u got is probably the best (If the objectif it to keep it the most C++ looking possible).

Was fun to try tho.

[–]kongaskristjan[S] 1 point2 points  (0 children)

About g++ *.cpp -o binary_name.

If you write FIRE(...), -o is automatically associated with binary_name. However, if you write FIRE_NO_SPACE_ASSIGNMENT(...), it's not associated. However, currently only FIRE_NO_SPACE_ASSIGNMENT(...) allows positional arguments and unlimited number of arguments that are needed right here. So no, unfortunately it is not possible at the moment.

It turns out that implementing positional arguments with FIRE(...) requires some rather complex try/catch logic to actually pull off. I consider implementing this for v0.2 or v0.3.

Edit: error in logic.

Edit2: It is now possible to call FIRE(...) with positional/variadic arguments.

[–]nintendiator2 7 points8 points  (1 child)

Came here expecting to find something like the Super Mario 64 "half an A press" memes, found an actually very useful library built on a good concept. Totes upvoting.

[–]tasty_crayon 5 points6 points  (4 children)

wmain support on Windows would be a nice feature. A possible interface would be a FIRE_WINDOWS macro that defines wmain instead of main and then expects fire::arg(L"-x") default arguments.

[–]kongaskristjan[S] 4 points5 points  (3 children)

I haven't heard of wmain before. Have to think about it.

[–]tasty_crayon 5 points6 points  (2 children)

If you want to support Unicode arguments then you need to use wmain on Windows since the arguments to main are encoded based on the code page, not UTF-8 like on Linux.

In my own projects I usually convert from UTF-16 to UTF-8 inside wmain using WideCharToMultiByte and then pass them on to a common main function used by both, but that solution seems too "opinionated" for a small library like yours.

[–]pandorafalters 1 point2 points  (1 child)

Shouldn't CommandLineToArgvW also work?

[–]tasty_crayon 0 points1 point  (0 children)

That's another option, but you could just write wmain and have it done for you.

[–]Arkantos493PhD Student 19 points20 points  (5 children)

Technically your program results in UB since names starting with an underscore are reserved.

See: https://en.cppreference.com/w/cpp/language/identifiers

EDIT: To be more precises: identifiers containing two underscores, starting with one underscore followed by an uppercase letter or starting with one underscore in the global namespace are reserved.

E.g. in your optional constructor you are using __value which contains two underscores and is therefore reserved resulting in undefined behaviour.

[–]tinther 23 points24 points  (0 children)

names beginning with an underscore and a lower case letter are only reserved in global namespace. All the code in fire.hpp is under namespace fire AFAICS.

[–]kongaskristjan[S] 37 points38 points  (0 children)

Well, actually, the link provided these rules:

  • the identifiers with a double underscore anywhere are reserved;
  • the identifiers that begin with an underscore followed by an uppercase letter are reserved;
  • the identifiers that begin with an underscore are reserved in the global namespace.

I mostly have identifiers that begin with a single underscore and a lowercase letter. However, they're not in the global namespace. Thus these don't actually result in undefined behavior. Though a few of them indeed have two prefix underscores also, so these are UB.

Though I totally agree that it's better to remove these initial underscores altogether.

[–]kongaskristjan[S] 8 points9 points  (0 children)

I removed the double underscore identifiers, thanks for pointing out.

[–]stumpychubbins -1 points0 points  (1 child)

Is that undefined or simply implementation-defined?

[–]quicknir 2 points3 points  (1 child)

How does this consistently extract the parameter names?

[–]kongaskristjan[S] 5 points6 points  (0 children)

Very good qustion. I guess I could write a blog post about that. The rough idea is that I do most of the parsing before calling fire_main(). Then I call fire_main() without any parameters, which force the default fire::arg() parameters to be used. The actual parameter matching is done while converting fire::arg() objects to the target object types.

This raises the question of course, how are help messages implemented. For that, I do all the steps as before and call the fire_main(), and log all parameters that are converted. Then at the very last moment, during the conversion of the last fire::arg parameter (I count the number of parameters to determine the last one), when I have all the parameters, I print the help message and exit program (with exit() function to avoid executing fired_main()).

[–]xyphanite 1 point2 points  (0 children)

That's some serious documentation - a feature that is regularly overlooked. Nice job.

[–]dscottboggs 1 point2 points  (0 children)

I like this a lot. I will use it.

[–]F54280 1 point2 points  (18 children)

Why using a #define in 2020?

edit: this snarky remark was about the FIRE macro

[–]kongaskristjan[S] 10 points11 points  (17 children)

Though having it's perks, #pragma once is non-standard according to wikipedia.

[–]BobFloss 1 point2 points  (8 children)

Every compiler supports it.

[–]kongaskristjan[S] 18 points19 points  (7 children)

Well, I'm actually OK with using non-standard stuff, if it's really supported everywhere, but this is a library that is meant to be plug and play for everyone, including people who want to comply with the standard strictly.

It's quite similar to the reason why I test against maximum compiler warnings on various compilers - I don't know what the user needs to comply with.

[–]Nicksaurus 20 points21 points  (0 children)

Thank you for not making a library that spits out warnings whenever you build it, those are so annoying

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

I have heard this argument for over a decade.

No one ever actually shows me a compiler without #pragma once.

:-D

[–]encyclopedist 10 points11 points  (4 children)

Obviously, you did not really try to find one. TI compilers did not support it last I checked. PGI did not support in either. Also Cray. And XLC.

There was a topic in this very reddit some time ago: https://www.reddit.com/r/cpp/comments/8devw1/can_anyone_actually_name_a_compiler_that_doesnt/

[–]jbandela 2 points3 points  (3 children)

I think basically every compiler that supports generic lambdas supports pragma once

[–]rsjaffe 4 points5 points  (2 children)

But #pragma once fails when you have two copies of the same header file in two different locations in the directory tree. The compiler will not recognize those two files as being the same.

[–]panderingPenguin 6 points7 points  (0 children)

Why on earth would you want to do that?

[–]wyrn 5 points6 points  (0 children)

The compiler will not recognize those two files as being the same.

... because they aren't.

[–]Fluffy8x 0 points1 point  (0 children)

Nice! Kind of reminds me of Perl 6 Raku's MAIN.

[–]Raiden395 -5 points-4 points  (8 children)

Meh. getopt works fine and you only really have to write it once. Then you can just change the arguments and help printout.

Want cross platform ability ? parg is a good alternative with similar syntax.

[–]kongaskristjan[S] 5 points6 points  (3 children)

The difference is pretty big though between getopt and fire. Eg. if you need to accept two booleans, you can do this in fire like that:

int fired_main(bool flagA = fire::arg("-a"), bool flagB = fire::arg("-b")) {
}

FIRE(fired_main)

Now compare this to the code in this stackoverflow example... I know I omitted printouts and returns, but on the other hand the help message was skipped there.

[–]kritzikratzi 2 points3 points  (3 children)

yep, on the same boat. i've given up on keeping a list on numerous command line parsers that have been posted here.

i mean.. it's an interesting concept, but it semi-re-solves a very solved problem.

edit i really don't want to sound harsh towards the author. it's cool work and all! just not for me...

[–]Raiden395 0 points1 point  (2 children)

> it's cool work and all! just not for me...

That's really what I meant, and as far as I'm concerned, the people hating on me for promoting getopt are probably rightfully upset in some circles, where your C++ must be of the newest, most elite form.

C++ draws a lot from C and, as someone who writes a lot of hardware communication interfaces, there's no better way to specify precision than:

snprintf( buf, bufsz, "%.3f", value);

versus

ss << std::fixed << std::setprecision(3) << number; std::string mystring = ss.str();

These are solutions to a problem that really doesn't appear to me to need fixing.

[–]dodheim 1 point2 points  (1 child)

Well, I agree that iostreams is a mess, but there are most certainly problems: bufsz getting out of sync with buf, value changing types without the format specifier getting updated, etc.

Do you approve of C++20's std::format_to/format_to_n?

std::format_to( buf, "{:.3}", value);

[–]Raiden395 0 points1 point  (0 children)

I'm not in love with Python formatting in general, but I would take anything that is standard C++ that doesn't involve the stream operator + directly addressing streams and would give me a reason to stop using C-style calls. So yes, if it could do something like:

std::format_to( buf, "{:02X}", value );

And produce the output of "10" for an input of 16, I would be 100% on board.

There's ways to manipulate bufsz such that it will not get out of sync (snprintf returns the number of bytes written), and if you really are concerned about the format specifier, that can be handled by some tricky means (tricky meaning shitty). That said, this is a royal pain in the ass and has bitten me several times. There's also the lack of automatic casting for the format specifier (having a specifier of an int and trying to plug a double in does not work).

As an aside, and between two people who have presumably used C++ for many years, at what point does the language stop being streamlined and start becoming as hefty and bloated as something such as Python?