9 ms·
C++17 Features That Will Make Your Code Simpler
- evanpw 8y agoMy biggest complaint with structured bindings is that you have to give a name to every element, so if you're compiling with -Wall, you get a lot of "unused variable" warnings. Similar functionality in other languages lets you use _ to mean "I don't care", but in C++ that's considered just a regular single-character variable name.
- cannam 8y agoMine, tentatively, is that the binding is made by declaration order, not by name: struct { int a; int b; } x { 1, 2 }; auto [b, a] = x; Now b is 1 and a is 2, even though in the original structure b was 2 and a was 1. This is contrary to how most other languages with this feature behave. (I wrote a bit about this once: https://thebreakfastpost.com/2017/11/15/c17-destructuring-bind/ https://thebreakfastpost.com/2017/11/15/c17-destructuring-bi...) I haven't had the opportunity to write any C++17 in production yet, so I'm not sure how dangerous this dangerous-looking thing actually is.
- andrepd 8y agoIs it, though? x.a and x.b, and a and b, name different things. Why should they be correlated? Apart from ML, I which pattern matching works differently, I don't know of any language where destructuring tuples tries to match names rather than position (even in ML, if you pattern match tuples rather than records).
- cannam 8y agoBecause this is a struct, not a tuple. Historically at least, I expect C/C++ structure elements to be identified by name rather than by declaration order. Changing the declaration order ideally would not silently change the meaning of code that uses the struct. I realise that C++11 structure initialisation syntax already blurs this distinction. That's already a bit dangerous in my view, although in both cases I can understand why they chose to do it that way. Are there other languages in which struct (not tuple) bindings happen by declaration order?
- jcelerier 8y ago> Historically at least, I expect structure elements to be identified by name rather than by declaration order. then... just use the struct directly if that's what you want ? But a single binding can map to multiple structs, especially in generic code : template<typename T> double dist(T point) { auto& [x, y] = point; return std::sqrt(x * x + y * y); } should work with T's that look like struct Point { float f[2]; } or struct Point { float x, y; } or struct Point { float p1, p2; } or std::tuple<float, float> or ...
- HelloNurse 8y agoThis behaviour is the only reasonable one; "auto [b, a] = x;" cannot be considered wrong in any theoretical or practical way. The b and a in "auto [b,a]" are arbitrary names you are applying to the result of the destructuring assignment, and they are essentially unrelated to the names of the struct members. If you write "auto [apples, oranges] = x;" it means exactly the same thing as [b,a] and [a,b]: two ints, each with an arbitrary identifier, the first with value x.a and the second with value x.b. The only unusual and slightly flaky thing that's going on is interpreting a struct as a sequence; the order of struct fields is usually relevant for its memory layout, but not for language features that use identifiers. What you appear to want is something entirely different: constructing something that automatically mirrors the names of the struct members. For example Java reflection allows enumerating the fields in a class, getting their names and other metadata, and extracting from an object the value of a field of its class. You could then populate a map with a destructured copy of an object in which each value has the same name as in the original object.
- cannam 8y agoYour reply is written as if you think my objection is absurd - then in the middle of it you concede exactly the thing I'm objecting to, and describe it as "unusual and slightly flaky"! I do get what you're saying; I can see why it's done this way. C++ lacks tuples at the language level, so there is no other way to do an anonymous tuple-style binding. Being forced to bind to the same name as the structure element would be inconvenient for various reasons. It makes sense. I just think it's problematic that changing the declaration order of a struct can silently break code that uses it. And it is different from other languages - not just natty ones like ML but also mainstream ones like Javascript ES6 - which makes it a point of some curiosity. > What you appear to want is something entirely different: constructing something that automatically mirrors the names of the struct members. It's all just a question of declaring and assigning local variables, there would be no mysterious objects or runtime reflection involved in either case.
- HelloNurse 8y agoNo, it doesn't make sense. Order coupling between destructuring assignment targets and class fields is very similar to order coupling between formal and actual parameters of functions, which is easier to get wrong and for which no protection exists. Struct X could have a constructor public X(int first,int second):a{first},b{second} but a caller intending to set a=1 and b=2 could call X(2,1) instead (particularly if last time they checked the constructor was public X(int first,int second):b{first},a{second}). You certainly don't want to prevent this "mistake" by enforcing that the first parameter must be a local variable called a and the second must be a local variable called b and that the constructor parameters must be called a and b too. This sort of easily detected error is exactly the same as writing [b,a] instead of [a,b] in a deconstructing assignment. Treating a sequence of fields in a class as a tuple is "unusual and slightly flaky" because few and unimportant languages do it, because it's very new for C++, and because it faces complications like inheritance, not because it is wrong or bad; someone might be slow to adapt, but everyone else will benefit. In practice, only very plain and well documented structs will be suitable for deconstructing assignment; for example, in code generated from data serialization schemas there is a natural and well known order for class members.
- andrepd 8y agoIn std::tie assignments you can use std::ignore. Not sure if there is an equivalent for structured bindings.
- evanpw 8y agoUnfortunately, no: https://stackoverflow.com/a/40714311/4807623 https://stackoverflow.com/a/40714311/4807623
- edanm 8y agoEvery time I read about modern C++, I'm amazed at how different the language looks compared to how it was when I last used it heavily, around 10 years ago. It's had amazing progress, it looks way more "modern" and really seems like a brand new language.
- mromanuk 8y agoI was using it 20+ years ago (highschool) then used it in my previous startup (10 years ago). Amazing how it keeps evolving and modernizing. auto typing keeps growing, reminds me of Swift.
- pjmlp 8y agoWhile true, many of the modern idioms were already possible in C++98 when you look at what was being done in OWL, VCL, CSet++ and other similar libraries. I mean, even RAII was already a thing in MS-DOS C++ code bases. However, many kept using it as if it was C with some extras.
- mhd 8y agoIt didn't exactly help that "many" included the platform toolkit authors. MFC made me look at Motif fondly. Never mind compiler support. I just recently noticed with delight that I finally forgot the exact VC #pragma numbers to disable all the STL warnings. Which gives me hope that one day I'll manage to do the same to ORA-00907.
- pjmlp 8y agoYep, agreed. MFC wasn't something I would list as good C++ GUI examples. Unfortunately Borland just made a mess of their customer base. Even today, Visual C++ is not as visual as C++ Builder.
- coldcode 8y agoI haven't done an C++ in about 6 years, and that was basically a mix of horrible C++ from the 90's and C. It's nice that that language has evolved. I still prefer Swift (or Rust or Kotlin or even Go for other things). I don't miss C++ at all. C++ used to be the only real choice for high performance code (like games, which that earlier referenced codebase was) but today there are many more choices.
- mlthoughts2018 8y agoWhat is the intended use of auto in these cases in a statically typed language? It seems counter-productive because in C++ writing type annotations is an aide to the code reader to understand the code author’s intent. Making the code reader also infer types of constituent variables, and remember those types in their head as they read the surrounding code where some e.g. structured binding was used to assign to some variables — this seems like a very bad thing to do to code readers and you’re gaining hardly anything in terms of terser syntax. Compared with Python, say, where you always know that the burden of understanding how a variable is used is part of the reader’s responsibility, maybe with conventional help in docstrings or optional type hints, this C++ feature seems like the worst of both worlds. You still have to add some annotation (“auto”) and keep in mental cache an understanding of the types, instead of just being nice and explicit about it and declaring things with the types. So you don’t get the freedom of duck typing on the auto’d variables, and you trade for slightly reduced syntax instead of explicit annotations for the reader. It seems like it would immediately become a C++ anti-pattern to use structures binding. It’s cute instead of pragmatic.
- tzahola 8y agoYou assume that the point of using types is readability, which is false. Types let the compiler reject a large set of invalid programs, which would otherwise produce a runtime error in a dynamic language. for (auto foo : foos) { foo.bar() } I don't care about the exact type of `foo`, so I let the compiler deduce it behind the scenes. However, I still want it to break my build if `bar()` doesn't exist!
- mlthoughts2018 8y agoYour example does not convince me. As a code reader, I would care very much why does foo have a bar() method. Is it because foo is a Widget or a subclass thereof? Then I can backtrack to understand the implementation being inherited. Or maybe it’s some other class that uses bar() for a totally different purpose. Who cares if the compiler can deduce it. That’s not helpful to me at all if I am scanning this section of code because I have to add more method calls inside the loop to calculate new results, and I don’t know what base type foo even is to get started with what would be required. > “You assume that the point of using types is readability, which is false.” This just seems wrong to me. Communicating your designed intention with type annotations is the number one reason to use them, whether you’re writing C++ or Haskell. Getting the compiler’s help to prove correctness or avoid bugs is a secondary reason. Sometimes it becomes the primary reason in isolated cases. But usually it’s a secondary consideration nowhere near as important as using types to communicate the design of the program.
- ancarda 8y agoI'm completely ignorant about C++, so I will ask this question: What's the state of memory management in modern C++? Is it easier to avoid shooting yourself in the foot? Can you write code that is completely safe? I've always flat-out avoided C++ because I don't think I will be able to handle memory management for a large program; It'll be riddled with security holes and memory leaks. Unfortunately, many OSS programs I want to contribute to are written in unmanaged languages like C or C++. As far as I'm aware, it's easier to get memory right in modern C++ compared to C, which hasn't received many updates.
- tzahola 8y ago>Can you write code that is completely safe? Yes. Just don't use raw pointers. Use unique_ptr, shared_ptr and weak_ptr instead.
- ant6n 8y agoAlso, keep things on the stack (C++11 onward has features that allow avoiding copies).
- sclangdon 8y agoThese things don't make it safe in the security sense. They just help against memory leaks.
- beojan 8y agoI don't think you can ever write code that's completely safe then, if nothing else some processor flaw will come along and ruin everything.
- dbaupp 8y agoWhile it's true that processor flaws destroy the assumptions that higher level components (such as any/all programming languages) build on, you don't need to go nearly that far to see unsafety in C++, even using only the most modern techniques: use-after-move of many types is undefined behaviour (for instance, dereferencing a std::unique_ptr that has been moved from), and iterator invalidation & dangling references aren't addressed by those smart pointers at all.
- deleted 8y ago[deleted]
- makecheck 8y agoThe code examples are great but I also want examples of compiler errors when things go wrong. C++ sort of has a reputation for compilers omitting messages that are, to put it mildly, unhelpful and verbose. While they’ve improved over the years, I’d like to know if new features have new confusing error messages. Saving 30 seconds writing a one-liner doesn’t really “count” if there’s a chance of seeing a 40 line template-unrolling when deduction fails due to a typo. Does that happen here?