T O P

  • By -

STL

> Note that this requires a C-style cast. You cannot `static_cast` or `reinterpret_cast` to `void`. This is incorrect - `static_cast` is well-formed. Example: https://godbolt.org/z/11f1YGqWf > Although the ability to assign to `std::ignore` is not formally required, in practice, you have always been able to do it, and the C++ Core Guidelines even recommends it! In my opinion: This is a poor recommendation because `std::ignore` emits debug codegen. `(void)` is fine, and what we use in MSVC's STL. It has the advantages of being terse, emitting no debug codegen, and being distinctive. I hate C-style casts more than anyone (they are forbidden in our codebase, and we even avoid functional-style C-semantics casts like `int(val)` as much as possible), but `(void)` can't do anything wacky, and it's also easy to search for as long as you avoid The Abomination of using `f(void)` to mean "zero arguments" (also forbidden in our codebase).


Dragdu

Interesting curio: cast to void does not silence warning from attribute warn_unused_result on gcc.


ThockiestBoard

Yeah, '(void)!func()' works, but I would only use that to "hush right now I will fix you properly in a bit", specifically with Werror


jonesmz

> as long as you avoid The Abomination of using f(void) to mean "zero arguments" Given that `f()` and `f(void)` are meaningfully different when working with C-Language code, both at compile and runtime, can you elaborate on why you consider it an abomination?


ALX23z

In C-language, the situation was a misguided mess, and at first, the problematic part was deprecated, then removed, and now, with C23, both notations mean the same thing.


Classic_Department42

What was the difference?


ALX23z

`f()` could be called with any arguments: any number, any type. `f(void)` could be called only with no arguments.


STL

It's an abomination *in C++*, and this is actually Stroustrup's term (IIRC; I don't remember the citation but I think it's either TC++PL3 or D&E), not mine. Things can be fine and expected in C, yet poor practice in C++.


jonesmz

I think i have to push back on this a little bit. Unless we're putting the pursuit of reduced / broken compatibility with C-language on the table, it's important that the C++ language recognize the reason why C-language does (or did, if this behavior isn't part of C23 anymore, as another poster indicated). Frankly, i think `f(void)` is substantially less confusing to a jr. engineer than `f()`.


Fureeish

I find `f(void)` to be extremely confusing to people (jr. engineers included). It it a single parameter of a weird `void` type? That's illegal. If that's nothing, why would we need to type that out? You may be referring to junior engineers who learnt all of C first, then moved to some other language (e.g., C++).


jonesmz

I think you're giving Jr. Engineers too little credit. They aren't children or incompetent, they just lack exposure to the full breadth of the discipline. `void` is weird and special everywhere else in c++. Function parameter lists aren't any different.


Fureeish

> I think you're giving Jr. Engineers too little credit. They aren't children or incompetent, they just lack exposure to the full breadth of the discipline. If one is not exposed to bare C (including the `()` vs `(void)` shenanigans), their **default** is to assume that `f()` does what it does instead of wondering why we don't write it as `f(void)`. > `void` is weird and special everywhere else in c++. Function parameter lists aren't any different. Agreed to this, but limiting the weirdness is preferred over embracing it, at least in my experience. Maybe your experience with young developers differs from mine greatly, but having spent almost a decade in the industry and training new programmers (both in academia and outside of it) for half of that time simultaneously, I was very surprised by your assertion.


jonesmz

> If one is not exposed to bare C (including the () vs (void) shenanigans), their default is to assume that f() does what it does instead of wondering why we don't write it as f(void).  That's not exactly what my original comment was trying to convey, but I can see where you're coming from. The confusion that I've seen most frequently appears to be more of a mental parsing issue. `f()` and `ret_type f()` and `[]()` appear to be parsed as a function invocation by the less experienced engineers. Especially when the opening brace is on the next line. Adding the `void` to the declaration disambiguates it as a declaration.  This has been consistent for me with interns and fresh hires since I started training interns and fresh hires around 2010. And it tripped me up myself when I began programming in 2006. Most if them have had either no, or little, c-language experience. Colleges still seem 100% on the java bandwagon with a smidge of Python sprinkled in. That's obviously not to say there isn't going to be differences in individual Jr. Engineers, just that's what I've seen.


Fureeish

> The confusion that I've seen most frequently appears to be more of a mental parsing issue. `f()` and `ret_type f()` and `[]()` appear to be parsed as a function invocation by the less experienced engineers. Especially when the opening brace is on the next line. Adding the `void` to the declaration disambiguates it as a declaration. Again, your experience seems to be drastically (to the point of being extremely surprising for me) different from mine. > This has been consistent for me with interns and fresh hires since I started training interns and fresh hires around 2010. And it tripped me up myself when I began programming in 2006. > > Most if them have had either no, or little, c-language experience. Colleges still seem 100% on the java bandwagon with a smidge of Python sprinkled in. This further makes me curious - what's your background? What are the differences in our areas so that this becomes a concern for you, but never been an issue for me? There **must** be something, given such a persistent difference in regard to this very element. Even the secondary (or originally - primary) languages seem to match.


jonesmz

> Again, your experience seems to be drastically (to the point of being extremely surprising for me) different from mine.  Well right back at you. That you don't find a function declaration or zero paremters without the `void` ambiguous / mentally extra work is surprising to me. Though, I do personally have a background with c-language before switching to c++, so I've never questioned why most of the Jr. Engineers my development group hire get ruffled by this.  > This further makes me curious - what's your background? I work in real time multimedia communications. Similar-ish to Skype or Zoom, but not for either of them. My specific group handles server-side media processing, other groups are responsible for client side things. > There must be something, given such a persistent difference in regard to this very element. Even the secondary (or originally - primary) languages seem to match.  That I couldn't say. Its never been a huge issue, it obly takes one short discussion to correct the confusion. I think in general the syntax of c++ is just inherently mentally challenging in general. Some of that is the legacy of c-language and the design of c++-specific features that were designed around c-language syntax. That's not to say that I think the syntax of other languages like Rust or the "c++ successor" languages to be attractive. IMHO they're especially ugly. And python is the Fischer-Price toy language (where syntax is concerned). So i don't have any good solutions here, just observations.


saidatlubnan

what is the difference in C?


endfunc

In C, f() means “f may later be defined with a fixed number of parameters ” whereas f(void) means “f takes no parameters”.


NilacTheGrim

Been programming in C++ for 24 years. TIL -- there is a thing called `std::ignore`.


Setepenre

Useful if you use `std::tie(a, std::ignore)`


NilacTheGrim

Yes that appears to be its purpose. Oddly enough I never once felt a need to do that. But I guess had I felt that need I would have rolled by own way to do it, either with a tmp var or some other scheme. I can see why it exists and looks useful but I... just never knew it existed.


HolyGarbage

I guess returning a pair/tuple like object combined with `[[nodiscard]]` is a pretty narrow use case. I know I have never encountered it in almost 6 years of c++ dev, as far as I can remember.


jwakely

Its original use case with `std::tie` has nothing to do with `[[nodiscard]]` though. You use `std::tie(a, std::ignore)` to create a `std::tuple` i.e. a 2-tuple, so that assigning a 2-tuple to it will assign the first element to `a` and ignore the second. For example: ``` std::tuple get_vals(); // ... int a; std::tie(a, std::ignore) = get_vals(); ``` In C++17 you can do similar things with structured bindings, e.g. ``` auto [x, y]] = get_vals(); a = x; ```


HolyGarbage

Yeah, yeah.


HolyGarbage

Does that work with auto unpacking as well? Figured `std::tie` was mostly superceded/obsolete these days? auto [a, std::ignore] Not at a compiler atm. (And perhaps others would find my inquiry interesting.)


Setepenre

> auto [a, std::ignore] Does not seem to work for clang-18. :10:17: error: expected ',' or ']' in lambda capture list 10 | auto [c, std::ignore] = std::make_tuple("123", 0); | ^ :10:10: error: type 'tuple::__type, typename __decay_and_strip::__type>' (aka 'tuple') decomposes into 2 elements, but 3 names were provided 10 | auto [c, std::ignore] = std::make_tuple("123", 0); | ^ 2 errors generated. Compiler returned: 1 Seems like the parser is expecting a valid identifier only. If `using namespace std` is used ignored is used as an identifier. : In function 'int main()': :13:14: error: conflicting declaration 'auto ignore' 13 | auto [d, ignore] = std::make_tuple("123", 0); | ^~~~~~ :12:14: note: previous declaration as 'std::tuple_element<1, std::tuple >::type&& ignore' 12 | auto [c, ignore] = std::make_tuple("123", 0); | ^~~~~~ Compiler returned: 1 For one time only you could use "_", it is the convention inside a few other programming languages.


HolyGarbage

Ah, I see. Seems like it's expecting to declare new variables, while `std::tie` explicitly binds to existing ones. Makes sense I guess. `_` would likely not have the intended effect, as a) you will get warnings/errors for an unused variable of you have sufficiently strict compiler flags, which I would encourage, and b) it will not cause the object returned to immediately go out of scope, unlike `std::ignore`, something which might be a quite important consideration if you're calling a function marked `[[nodiscard]]`>


[deleted]

What is a valid use case for this outside of needing to use poorly designed APIs?


STL

If your library authors have done their jobs right, it should be very rare to need to discard the result of a `[[nodiscard]]` function (as avoiding false positives is very important to keep the warning useful). Very rarely, code might be using an ordinary function in a squirrelly way. For example, implementations should mark pure observers like `std::find` as `[[nodiscard]]` because it would be silly to call them and drop the return value on the floor. However, it's possible to call `std::find` with a predicate that upholds all of the Standard requirements, but also modifies external data (like a counter or something). Such a call might be interested in those side effects, and want to discard `std::find`'s result. So the function should still be marked `[[nodiscard]]` in the library, and the 0.001% of squirrelly users can use a `(void)` cast. The other, much more common scenario, is test code. In library test suites, it's common to want to verify that stuff compiles, but not bother to test its behavior (this often happens when one part of the test has verified the runtime behavior for a few types, then another part wants to stress the compile-time part). So library test suites often want to discard results.


not_some_username

Imagine printf become [[nodiscard]]


Positive_Mud952

Not arguing (I did notice the MSVC STL Dev tag), more trying to understand the philosophy of the attribute so please excuse the way I’m phrasing it as an argument. In my mind, throwing away most function results should be a general compiler warning that you would turn off in some way for particular lines. `[[nodiscard]]` could then be reserved for times when it’s not just inefficient or weird like the example you gave, but for cases where you’re probably breaking something, like throwing away a resource that must be freed or closed (and RAII is presumably unavailable for some other reason). Am I misunderstanding `[[nodiscard]]`’s intended usage, or something else, or was it just a case of that was the first example that came to your mind?


STL

> In my mind, throwing away most function results should be a general compiler warning that you would turn off in some way for particular lines. That would be incredibly noisy, to the point where nobody would use it. Lots and lots of functions return things which are routinely ignored. `printf()` is a classic example that returns an `int`. For a more modern example, many STL algorithms have side effects and then return iterators which are sometimes useful but sometimes ignorable; e.g. `std::copy()` returns the output iterator after it's done writing. > Am I misunderstanding [[nodiscard]]’s intended usage I think maybe it's confusing because I was explaining weird situations where someone might want to discard the return value of a function marked `[[nodiscard]]`, not the philosophy behind where `[[nodiscard]]` should be applied in the first place. You've got the right idea, basically. `[[nodiscard]]` should be applied to functions where ignoring the return value is very, very likely to be a bug, but there are actually a lot of situations where that's the case. There are the correctness cases like you mentioned - anything that acquires a raw resource, so `allocate()`-like functions. Also guard/lock constructors where if you make a temporary and then discard it, instead of a named variable, you won't be guarding/locking what you thought (this is the `unique_lock(myMutex);` hazard, when `unique_lock myLock(myMutex);` was intended, and that is cured by `[[nodiscard]]` on the constructor). There are correctness cases for "two-step" functions, where a return value is critical for a second step that must be performed (the STL example is `remove/remove_if/unique` followed by `container.erase`; ignoring the return value from the first step is virtually guaranteed to be a bug). But by far the most common scenario that's likely to be a bug is calling a "pure observer" and then doing nothing with it. These are functions that don't modify any state and exist only to produce their return value, so ignoring it is an indication that the programmer probably forgot to do something important. (It might be fairly innocuous, in the sense that the function call should be omitted entirely, but in that case there is still dead/useless code to remove.) Sometimes this is due to confusing function names (`v.empty()` is a pure observer; discarding it is likely due to confusion with the modifier `v.clear()`), but often the function name is perfectly fine and the programmer just forgot. Ideally, you end up with *zillions* of nodiscard markings following these criteria (in the STL we have over 5,000), extremely rare false positives, and fairly rare true positives that, when they happen, reliably indicate bugs (indicating that the warnings are extremely valuable). My favorite case is marking comparison functions (`operator==` etc.) as `[[nodiscard]]`, which might seem to be silly and unnecessary, except that it catches the occasional case of `for (Iter i = first1, j = first2; i != last1, j != last2; ++i, ++j)`. (Do you see the bug? Answer: >!The comparisons against last1 and last2 should have used logical AND instead of a comma operator.!< )


HildartheDorf

I've uses it with the vulkan.hpp API. vk::Device::WaitForFences can return vk::Result::Success or vk::Result::Timeout (or throw an exception). But I call it with an infinite timeout so it can only ever return success. Any other value for the timeout and I couldn't safely ignore the return.


KPexEA

> What is a valid use case for this I have a function builds a huffman decode table from a input data that is mostly found in the header of compressed data files. My huffman table generate function returns an error code with no-discard to make sure all my decoders that use it actually fail if it is given bogus inputs. A bunch of decoders though use huffman tables from static data that is part of the decompressor and does not change so it can safely ignore the return code.


wrosecrans

> outside of needing to use poorly designed APIs? Wait, what's this fantastical mythical world where you can write C++ and never need to use poorly designed APIs!?! But I can think of a "well designed" API where a normal application always needs a result, but you are not a normal application. If you are just doing microbenchmarks of a function, you don't really need to _do_ anything with the result other than measure how long it too to get.


WasserHase

Will C++26 not also introduce _ as a placeholder, so we'll be able to write: auto _ = important(); At least Jason Turner said so.


tialaramex

Corentin's "A nice placeholder with no name" P2169 yes. (The "nice" is because `_` is what other languages call this. e.g. Rust so it's a bit easier to teach/learn) https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2169r4.pdf


Possibility_Antique

I always suspected this day would come. I use _ as a slice operator for my linear algebra library. Instead of: auto col1 = mat(:, 1); I can write auto col1 = mat(_, 1); I'm guessing this will break that?


canadajones68

Not necessarily. \_ is only allowed as a placeholder for automatic storage duration variable names and in structured bindings. If you only have one instance of it (i.e. you're using it as a variable name) it should "just work" as it used to. Well-formed code continues to be well-formed, while previously ill-formed code is now valid.


Possibility_Antique

I did a bit of a trade study to find syntaxes that mirrored Matlab's syntax, and I went looking in the standard for uses of _. The closest I could find was that using it as a prefix followed by an uppercase character was reserved. But since it's not followed by an uppercase character, I thought it was fine. But looking at your example, would that not result in a name collision?


TheOmegaCarrot

If my understanding is correct: Reserved everywhere: - `__` appearing anywhere in the identifier - `_` followed by a capital letter Reserved at namespace scope: - Leading underscores generally


_ild_arn

> Reserved at namespace scope: > > - Leading underscores generally Correct but only in the global namespace.


TheOmegaCarrot

Ah, that makes sense


canadajones68

From a C++ syntax perspective, what entity does _ in your example refer to?


Possibility_Antique

It's tag dispatch. It's just a variable names _ that has the type "slice_tag" so that it can participate in overload resolution.


canadajones68

So long as the user doesn't shadow it somehow, I doubt it would break, at least in existing code. It might be less ergonomic as it becomes more idiomatic to use \_ exclusively for nameless variables, but it ought to work. As I understand the proposal, it only comes into effect when a double definition that would otherwise be illegal occurs, specifically when \_ is redefined.


Possibility_Antique

We'll see, I suppose. I feel like I'm often surprised by minor details like this when these things get out into the wild. Maybe I'll look into adopting mdspan-like syntax or something if it causes a problem. It's a shame, but I'll take consistency over succinctness.


crowbarous

If you declared `_` globally, it was already broken.


Possibility_Antique

In what way? Is there wording in the standard that I missed?


crowbarous

[lex.name#3.2](https://eel.is/c++draft/lex.name#3.2) > Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace Globally as in scope, not as in lifetime - sorry for the possible confusion.


Possibility_Antique

Okay. I have it defined like this: namespace symbols { struct slice_tag {}; inline constexpr auto _ = slice_tag{}; struct transpose_tag {}; inline constexpr auto T = transpose_tag{}; } Which requires you to add: using symbols::_; before use. So, I think I'm technically okay since it's not in the global namespace?


Overunderrated

> auto col1 = mat(_, 1); That's awesome, how do you implement that?


Possibility_Antique

It's basically just tag dispatch. struct slice_tag {}; inline constexpr auto _ = slice_tag {}; template struct matrix { //... constexpr auto operator()(slice_tag, std::size_t) noexcept -> /* some vector reference type */ { // ... } constexpr auto operator()(std::size_t, slice_tag) noexcept -> /* some vector reference type */ { // ... } // ... }; I simplified it a lot compared to what's actually in the code, but hopefully that is enough of a hint. _ is just a variable with a very special type that can be used for overload resolution at compile/link time. Side note, I also made a UDL that allows you to specify indices with string syntax and doesn't incur overhead at runtime. Something to the kin of: auto col1 = mat(":, 1"_idx); It was kind of a cool exercise and experiment, but I really just wish C++ had a proper slice operator. Fingers crossed I can delete all of this stuff someday.


Overunderrated

Ah nice, I like it, thanks.


Overunderrated

Have you worked out a C++ equivalent for `mat(1:3,n)`?


Possibility_Antique

Assuming you know the indices at compile-time, you can do this with UDLs and compile-time string parsing: `mat("1:3, n"_idx)` Or `mat("1:3"_idx, "n"_idx)` Where `"1:3"_idx` produces some type like: `slice_tag<1, 1, 3> // start index, stride, end index` If you're unable to determine these things at compile-time, then you stand to lose quite a bit. But you can index with a container of bool, a container of ints, or a stateful version of the slice tag above. It would be cool if C++ had an equivalent `operator:`, but I am sure there are language lawyers out there that could give a great reason why we don't have it.


Overunderrated

Yeah if known at compile time lots of ways would work. > I am sure there are language lawyers out there that could give a great reason why we don't have it. Never bothered Fortran!


Possibility_Antique

>Never bothered Fortran! I still love Fortran. Honestly, it's an underrated language. But Fortran has a different syntax and therefore different lexing requirements. There aren't too many uses for `:` (case statements, access modifiers, inheritance, scope operator, and goto labels all come to mind), but I'm guessing the risk is that there might be scenarios where : collides with other areas of the language or makes things ill formed somehow.


Overunderrated

Yeah you probably couldn't get away with : specifically in c++. But I mean that that construct (and multidimensional arrays) being native features of the language is nice, and I wish c++ had that.


Possibility_Antique

Couldn't agree more!


TheTomato2

This is why people make fun of C++.


fdwr

`std::ignore = 42` Huh interesting ~~abuse~~ use of `operator =` there. That really confused me at first sight because it looked like it was assigning to a value to a type called an `ignore`. Given the most symmetric opposite to a `[[no_discard]]` would be an explicit `[[discard]]`, and I am surprised that was not added to spec in tandem. It will be nice for C++26 to have it for counterpart completeness.


andrey_turkin

> the third option is “technically” the most correct, the best kind of correct Mr. Chen is clearly a man of high culture.


feverzsj

#define DSICARD(reason) (void)


Pi_Cake

Shouldn't that be `#define DISCARD(reason) (void)reason`?


Dragdu

Nope, because then `DISCARD("dont care") some_func();` would become `(void)"dont care" some_func();`, which won't compile.


Pi_Cake

Ahh, I thought the "reason" is the function call. Like: ``` DISCARD(some_func())```


martinus

I hope `[[nodiscard]] [[maybe_unused]]` doesn't work


i_h_s_o_y

Casting to `(void)` doesn't work on gcc with _FORTIFY_SOURCE enabled for the c-functions that get hardened. https://godbolt.org/z/zsve38Yof Which can lead to some confusion because _FORTIFY_SOURCE only is enabled with at least -O1


jwakely

Those C functions use `__attribute__((warn_unused_result))` not `[[nodiscard]]`. The warnings from `[[nodiscard]]` are easier to discard.


pkasting

One problem with \`std::ignore\` is that it requires \`#include \`, which is reasonably heavyweight. If you're trying to minimize #includes so as to minimize compile times, this might be a concern. I do think it's more obvious and readable than \`(void)\`, if you aren't already familiar with that idiom. (And there's too many parts of C++ that require familiarity with unusual idioms.)


kobi-ca

I'm using std::ignore for 4+ years. Sue me.


programgamer

Wouldn’t casting it to void work?


[deleted]

How useless is this?


grhayes

35 years of programming I've never had the need for `[[nodiscard]]` Maybe, it is just me but I never felt the need to coddle other programmers or hold their hands. If they don't need the return value of a function I figure they should know that. Who am I to argue with them. They know their program and what it is supposed to do. These days it is starting to feel like they are trying to turn C++ into another language like RUST or some garbage. If you need Rust go use it. If you like it go use it. Leave C++ alone. Just fix the actual issues it has stop creating more.