Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm glad this hasn't turned into (so far) the usual "c++ is dumb" flame fest.

I've really enjoyed programming in c++17 for the last three years (I had the luxury of starting from an empty buffer) and find the language pretty expressive. If you are able to use it as a "new" language, ignoring its C roots and features that exist pretty much just for back compatibility, it's really quite expressive, powerful, and orthogonal.

I'm actually pretty glad the c++ committee has been willing to acknowledge and deprecate mistakes (e.g. auto_ptr), to squeeze out special cases (e.g. comma in subscripting) and to attempt to maintain generality. Conservatism on things like the graphics standard is reducing the chance of the auto_ptr or std::map mistakes.



> it's really quite expressive, powerful, and orthogonal

Expressive and powerful are definitely true, but I'm not so sure about 'orthogonal'.

Most C++ features seem to depend heavily on each other, and library decisions can impose restrictions/boilerplate on application code in many areas.

Especially as the language has evolved, features like exceptions, destructors, copy and move constructors, and operator overloading have become more and more indispensable. Template functions are still required for any kind of variance (e.g. you still can't have a non-template function that works on a const std::shared_ptr<const *base_type> or any derived type).

None of these is unbearable, but I do believe that even the modern subset of C++ is still the least orthogonal mainstream language in use (and definitely the most complicated by far). To be fair, it's also likely the most expressive mainstream language too, and still the most performant.


> you still can't have a non-template function that works on a const std::shared_ptr<const *base_type> or any derived type

I guess you meant, you can't have a function that accepts a shared_ptr to the base type without indirectly using template machinery?

Because you can most definitely have a function that does this that is not a function template.

    #include <memory>
    #include <iostream>
    
    
    struct foo {
     virtual ~foo() = default;
     virtual void bark() const { std::cout << "fork\n"; }
    };
    
    
    struct bar : foo {
     void bark() const override { std::cout << "bark\n"; }
    };
    
    
    void poke_so_it_barks(std::shared_ptr<foo const> const thing) {
     thing->bark();
    }
    
    
    int main() {
     poke_so_it_barks(std::make_shared<foo>());
     poke_so_it_barks(std::make_shared<bar>());
    }


Oops, I picked a bad example it seems... Apparently `shared_ptr` does some magic to simulate covariance through type conversion. It still breaks in other cases (a function which returns a `shared_ptr<derived>` can't override a function which returns a `shared_ptr<base>`, unlike with derived* and base* ), but it still probably covers most real-world cases.

If I had stuck with std::vector my point would have been better. Even though theoretically a `const std::vector<derived* >` IS-A `const std::vector<base* >`, that is, a read-only view of a collection of derived elements behaves the same as a read-only view of a collection of base elements, you can't pass the first to a function expecting the second in C++, unless you make your own function a template.


In that case, you have a point. The closest you can get to covariance and contravariance is simulating it with implicit conversion operators/constructors (which then often need to be templates).

But I don't see how this could be implemented without completely changing large parts of the language.

In the one case of covariance that actually exists, overriding `virtual base* factory()` with `derived* factory() override`, the overridden function knows that the function it overrides returns a `base* `, so it can just return a `base* `, too, and callers through the subclass pointer know that they can adjust the return value back to a `derived* `.

But even the closest cousin, argument contravariance (i.e. overriding `virtual foo(derived* )` with `foo(base* ) override`) doesn't seem feasible. Callers through the base pointer will pass a `derived* `, so the overridden function has to expect to be passed a `derived* `, and there is no adjustment that can be made to a `base* ` to turn it into a valid `derived* ` statically.

The vector example is even more impossible. Since the function expects all members of the `std::vector<base* > const` argument to be `base* `, a caller with a `std::vector<derived* >` would have to allocate a new vector and adjust each individual pointer, which would be a terrible hidden runtime cost. (If they had added a constructor to accept a range to complement the one that takes an iterator pair, that's exactly what would have happened, though.)


> Since the function expects all members of the `std::vector<base* > const` argument to be `base* `, a caller with a `std::vector<derived* >` would have to allocate a new vector and adjust each individual pointer, which would be a terrible hidden runtime cost.

I don't think I understand your point. A function which expects a `base* ` can already be passed a `derived* ` without any problems. Doing the same for a collection of `base* ` to a collection of `derived* ` is not fundamentally different, but it does come with significant complications. For one, the language would have to be sure that the collection is read-only, since a function which wants to add something to the collection can't be called with a collection of a different type.

More significantly, the language itself has no idea that `std::vector<T>` is a collection of Ts, while `std::function<T>` is not a collection. In principle, it may be possible that it could look at all of the members and decide each template argument's variance. Even if that is theoretically possible (I'm not sure it is), it is certainly far too complicated to be a useful feature.

That leaves us with the option of some kind of annotation for variance in template parameters (or at use-time, as in Java).


> A function which expects a `base* ` can already be passed a `derived* ` without any problems. Doing the same for a collection of `base* ` to a collection of `derived* ` is not fundamentally different

Since C++ has multiple inheritance, derived* differs from base* not just by reinterpretation, the pointer value could be different.

A single pointer can trivially be adjusted, but a heap-allocated, potentially huge array of pointers would require an equally huge heap allocation to store the adjusted pointers.


Got it, thanks for bearing with me! I wasn't aware of this complexity of multiple inheritance (I guess this is another example of how orthogonal the language is :) ).


How could the type system reason about when covariance is valid?


It could potentially use const-ness for these use cases, though I'm not sure that would be tractable.

More realistically, it could allow the programmer to express it explicitly, as in C# (C<in T, out V> is covariant in T and contravariant in V) or Java (C<? extends T,? super V> is covariant in the first parameter, contravariant in the second). This would not solve the problem that the co-/contra-variance of some types may depend on their const-ness, but it would allow at least a covariant, always read-only, std::vector_view<T> (similar to IEnumerable<in T> in C#, which can also be implemented by the invariant List<T>/std::vector<T>).


The thing that has really kept me from getting behind updates to the C++ universe is the lack of progress on improving the state of build tooling. It is miserably underengineered for modern, dependency-heavy environments. C++20 does introduce modules, which is a good push towards correcting the problem, but I'm still going to be "wait and see" on whether the actual implementation pans out.


Well, there's Conan, which helps a bit, but these days what I simply do is use CMake to download a package from GitHub or wherever and build it.

Sadly the C++ ABIs are standardized the way that C ABIs are (I'm OK with why but it's unfortunate in that it creates a barrier) so you have to have separate libraries compiled with g++ and clang++ if you use both on your platform (we use both because they catch different bugs, and for that matter exhibit different bugs). But it means you can't simply install, say, fmt in any system-wide directory like /usr/lib or /usr/local/lib

Just as an amusing side note: Common Lisp used to be criticized for the massive size of its libraries and later likewise C++. It was true they were quite large. Now both are criticized for their tiny libraries. Which by today's standards they are.


you can definitely use c++ libraries compiled with clang++ when your code is compiled with g++ and vice versa. It only gets funky when one uses libc++ and one libstdc++


...unless those libraries use standard library datastructures, as you point out.


No, GCC and clang are fully ABI compatible (modulo bugs of course). Libstc++ and libc++ are not, so use whatever is the standard library of your platform (i.e the default for calng and GCC) and things work fine.


Oh I see; we were speaking past each other.

Yes, gcc and clang use the same platform API so if they use the same headers (libstc++ or libc++) then they will indeed use identical structure layout etc.

I meant a "gcc-native" toolchain (gcc + libstdc++) vs "llvm native" (clang++ + libc++) having different layout (and there is even some interoperability between them thanks to work by the llvm team). I realize my need to do this (to try to minimize opting bugs) is a special case, and probably unusual.


There is not really anything more “native” about using libc++ with clang as opposed to libstdc++ other than the fact that they happened to be developed by the same project. Using clang with libstdc++ is extremely mainstream and normal.

Actually I would bet that even among clang users, libstdc++ is used more commonly on GNU/Linux (IDK for sure, but it’s my hunch).


parent says they wants to use libc++ to catch more bugs. Which is a reasonable use case.


> Just as an amusing side note: Common Lisp used to be criticized for the massive size of its libraries and later likewise C++.

Part of "size of libraries" is "mental size of libraries".

And C++ and Lisp and have very large mental spaces for their main core libraries. A "String", for example, carries a huge amount of mental baggage in those languages. In most other languages, a string is extremely straightforward because it was designed into the language from the start.


It's possible I'm just used to it, but I've never found std::string more complicated than, say, python (what's up with unicode in 2 vs 3?) or JavaScript (UTF-16 = surrogate pair pain).

It's essentially a std::vector<char> with a few random convenience features bolted on.

I guess some of the confusing points are: not unicode aware, string literals aren't std::strings by default, c_str returns a pointer to a buffer with length one greater than the string length, and the usual C++ quirks like why is there both data and c_str?


> the usual C++ quirks like why is there both data and c_str

The usual C++ response: for backwards compatibility, because data was not required to null-terminate prior to C++11.


>In most other languages, a string is extremely straightforward because it was designed into the language from the start.

I think one of the classic advantages of C++ over C is that you have the option of std::string instead of char arrays.

I don't program in C++ so I don't really know but I do a lot of pure C and the strings truly are a mess (despite being VERY straightforward)


One of the things I like about C++ is that there is the std::string for common uses, but then you can design your own string classes with defined conversions to std::string. Qt adds QString with lots of nice utility methods, UnrealEngine adds optimized string types, etc. So you can have custom tailored classes for the task at hand, but easy to convert around to the different types with std::string conversions defined.


One of the things I dislike about C++ is that any large project will have lots of code converting between a dozen custom and gratuitously different string types.


This is the #1 thing I love about C++ compared with Rust — I don’t want it to be easy to depend on thousands of things. I would rather use a small, curated, relatively standard set of libraries provided by my OS vendor or trusted third parties.

“Modern, dependency-heavy environments” are a symptom of the fact that certain ecosystems make it easy to get addicted to dependencies; I don’t think they’re a goal to strive towards.


That's throwing the baby out with the bathwater. Building a C++ project with even a few critical dependencies (e.g. graphics libraries, font libraries, a client for a database or other complex network protocol) is a massive hassle. Sometimes those are curated well, sometimes they're not--but they remain essential for many projects. By taking a hard line against the hypothetical explosion of low-quality dependencies, you compromise the ability to use even the "small, curated, relatively standard set" of dependencies that are genuinely essential.


It's not about the ease of use (you need just a couple of lines in CMake to trigger the pkg-config scripts that every lib in the distribution has). It's about the people who work hard on maintaining the distributions. That's where the quality comes from.

And not only for the libraries:

http://kmkeen.com/maintainers-matter/


I'm not entirely convinced that this is a bad thing. The dependency-heavy environments a la Node.js gave us some interesting security nightmares.


Yes Node is a nightmare, however, you don't need to use public repositories, that much is a choice.

Similarly, Cargo in Rust is an absolute dream to work with, you just run `cargo new <project>` and then `cargo build`. That's it. If you want to add a dependency you can do so from the public crates.io repository or a sub-path or a git repository.

No language should be without this core feature, so it'd be great to see C++ get it too.


Package managers of the OS distributions just work, so many people don't see it as a "core feature" for the language.


That's not the same thing at all. Different versions, sources, etc. Should they really be he job of the OS package manager even when statically linked?


When statically linked it's even simpler: the dependency is used only during the build.


Maybe I wasn't clear, I'm advocating a solid build system and build dependency manager should be part of the core.


What does this feature have to do with programming languages? Why do I need one dependency tracking tool for Rust, a separate one for Go, a separate one for JavaScript, etc?

I already have a dependency management tool for C++; it is called the Ubuntu package repositories. I don’t need another one baked into the language.


Ubuntu and other OS package managers are years out of date and they are at the wrong layer for serious projects because they are OS layer so not good for hermetic builds on build servers.

https://repl.it/site/blog/packager


> No language should be without this core feature, so it'd be great to see C++ get it too.

BTW modules won't address this. I'm not sure you did but some other down thread comments implied that some people thought it would.


OS distributions have package managers and package repositories that have maintainers who are mostly decoupled from the developers. So that takes care of the quality/security problems that arise in ecosystems like Node.js.

There is also C. The tooling and the "package manager for C++" would be expected to seamlessly work for C and be expected to be accepted and used by the C community.

(personally I use CMake + OS package manager)


Although I agree with your point, cmake+vcpkg goes a long way for hobby projects, cmake with a bit of custom scripting goes a long way for larger scale projects.

The cmake language might not be beautiful, but it does allow for simple sub-projects/library scripts once the main cmake script is set up properly.


I think the general thinking is that in the c/c++ world dependency management is the role of the build tool

This is currently really easy to do cleanly though CMake by using Hunter and toolchain files (don't use submodules or addexternalproject)

External tools like Conan are also unnecessary bc they introduce redundancy and extra maintenance


> build tooling...is miserably underengineered for modern, dependency-heavy environments.

My advice about build tooling is to use buck (from Facebook) or bazel (from Google). If you have an ex-Googler nearby, use Bazel. If you have an ex-Facebooker nearby, use buck. Otherwise flip a coin.


While I love these projects, neither one of these actually fix the problem of an ecosystem of dependencies.

Buck and Bazel were created for complex internal dependencies.

You'll have just as easy/hard of a time getting Boost installed with Bazel as you would for Make.

Or publishing an accessible library that depends on Boost.


> You'll have just as easy/hard of a time getting Boost installed with Bazel as you would for Make.

> Or publishing an accessible library that depends on Boost.

This is patently false. Here is the bazel dependency for boost (put this in your workspace file):

    git_repository(
        name = "com_github_nelhage_rules_boost",
        commit = "6d6fd834281cb8f8e758dd9ad76df86304bf1869",
        shallow_since = "1543903644 -0800",
        remote = "https://github.com/nelhage/rules_boost",
    )

    load("@com_github_nelhage_rules_boost//:boost/boost.bzl", "boost_deps")
    boost_deps()
No other installation necessary. If someone has a C++ compiler and Bazel installed that will allow you to build against Boost deps (e.g. "@boost//:callable_traits"). No downloading boost by hand, no building boost by hand, no managing it's build, bazel does all of that.

Bazel does have a dependency ecosystem, it's based off of hashed versions of publicly available git repos and http archives (and anything else you want really). Which means any code anywhere can be a dependency while still being reproducible. Additionally you can provide local build instructions for the external code, so you don't even need to rely on their build. The better way is to find someone who maintains a BUILD file for a repo (or mirror and maintain it yourself) but still.


Boost may have been a bad example, since it is a common dependency, without any sub-dependencies.

To beetwenty's point, the C++ ecosystem in general lacks the the level of support for dependency ecosystems that you find in Maven, pip, gem, npm, etc.

Bazel external dependencies are in fact a real pain point. See the 3+ year old Bazel Recursive WORKSPACE proposal. [1]

I was at the first Bazel Conf in 2017, and the external dependencies meeting was quite the row.

[1] https://bazel.build/designs/2016/09/19/recursive-ws-parsing....


I feel like that's a misunderstanding of the issue. Recursive dependencies are orthogonal to the issue of having an ecosystem of dependencies. There is a pattern for recursive dependency management (did you notice the `boost_deps` recursive dependency call?) that is currently fine for production that recursive workspaces will make better.

Also as demonstrated Boost is way easier to include in bazel than make which was the original issue under discussion.

I make a library that depends on boost, here is how you use it:

    git_repository(
        name="foo"
        ...
    )

    load("@foo//:foo/foo.bzl", "foo_deps")
    foo_deps()
magic


The trick is that now one will depend on somebody who is not boost for these nice mappings. I brought such a dependency for gRPC once and now it doesn't work with bazel 1.0. I either have to find the author, understand all the bazel trickery, or switch to something else, because most of these bindings depend on many bazel features.

So, bringing such multiple third party bandaid is currently not such a great idea. It would be a bit better if boost itself provided it.


Wasnt this because the dependency for gRPC was moved into bazel itself?

How is this different than most other build systems? You always have to rely on others.


What kinds of problems do you run into with Boost on Bazel? Are there no available build files you can just drop and use directly?


Ah. Yes, I wasn't the one who had to get Boost into Buck, so I don't know the pain. But I also have been told to avoid Boost for modern C++, so...


I need boost, but mainly because I use other packages than depend on boost.

I also use boost when the compiler is behind (e.g. boost::variant until apple's compiler caught up).

I can't wait to have a boost-free tree. but I suspect it will be a while.


Why would someone avoid Boost?


I have seen a library with a copy of boost in the include folder. Client code is forced to use this outdated version and must avoid transitive dependencies to boost. Please don't do that.


It’s a very heavy dependency, in many ways.


Look into Bazel, so far it has been a dream.


What's the specific problem? Big companies run 100K+ .so/.o systems.


What does orthogonal mean as an adjective for a programming language? I've never heard that word outside of math and I always get excited by a word that might expand my ability to think about code.


Every feature controls one "dimension" of a language, and can be freely combined with other features that control other "dimensions" without worrying about how they interact.

I am quoting the word "dimension", because applying a precisely defined mathematics concept to something messier (like programming) will require a certain amount of hand waving and squinting.


I first came across the notion of orthogonality for programming languages in Algol 68.

Perhaps it's easiest to give examples of what it's not. At first in C you could only assign scalars; to assign a struct or union you had to call memcpy() or something like it. Eventually you could assign aggregates, but you still can't assign arrays unless you hide them in structs. It took a while before you could pass or return an aggregate.

Exceptions of that sort are non-orthogonalities.


One place where this shows in a fairly technical way is in discussing instruction sets: https://en.wikipedia.org/wiki/Orthogonal_instruction_set

"In computer engineering, an orthogonal instruction set is an instruction set architecture where all instruction types can use all addressing modes. It is "orthogonal" in the sense that the instruction type and the addressing mode vary independently. An orthogonal instruction set does not impose a limitation that requires a certain instruction to use a specific register[1] so there is little overlapping of instruction functionality.[2]"

Its use here is more general, but I hope the specific example helps.


Honestly I think it's a silly word to use for features. In math orthogonal means roughly "at right angles" or "dot product equals zero". When two things are orthogonal it means that they are unrelated so you can deal with them independently which makes things simpler.

In describing features orthogonal is basically jargon for unrelated and adds no additional clarity imo.


You don't want to write unrelated code, you want to write orthogonal code.


Orthogonal means that aspect are independent of other aspects.

I.e. you can understand one aspect of a programming language (memory allocation, generic programming, standard library, mutability, control flow, pattern matching, etc.) without understand every aspect.

As a counterexample, if a language has generics, but only generics for a standard array type, that lacks orthogonality.


Imagine a programming language that gives you three different ways to add 2 numbers:

1 + 2;

std::add(1, 2);

std::int(1).add(std::int(2));

That's is not orthogonal at all, since we have 3 different ways to do the same thing for no discernible reason.

An orthogonal interface is one where there are fewer reasonable ways to do something, and each function/operation has it's role clearly defined, it doesn't try to do things unrelated to its main role, and the interface doesn't mix different layers of abstraction (like intertwining OOP and arithmetic in my 3rd example, when there already are native arithmetic operations).


I recommend the book: The pragmatic programmer. There's a section there explaining orthogonality by explaining a system that is not: a helicopter.

That example sticks with me.


Not the OP but guessing that in this context probably means independent of historical baggage.


last time I used C++ was in the mid-2000s. With all the new features since then, I assume the "proper" way/style to right C++ has changed since then?


Largest changes (most from C++11, some from C++14 and C++17) are:

- std::unique_ptr and std::shared_ptr (your code should not have any new/delete). It's easy to write code with clearly defined ownership semantics, and I've yet to create a memory leak.

- Proper memory model so that multithreading has defined semantics.

- Lots of syntactic sugar (auto, range-for, lambdas, structured bindings, if constexpr, etc.).

- More metaprogramming tools/fun.

C++11 alone is a very different world compared to what you are probably used to.


> - std::unique_ptr and std::shared_ptr (your code should not have any new/delete). It's easy to write code with clearly defined ownership semantics, and I've yet to create a memory leak.

I cannot begin to describe how huge a thing this was for me. I came to C++-17 (just about) from C++-98 -- a language I hated so much, I vowed I would never, ever write anything in it on purpose.

It is a massively different beast today from what it was, but the biggest thing of all is this change in how you deal with memory. It was like going from a particularly annoying and verbose aberation of C to a particularly fast and expressive version of Python.

YMMV, of course. In fact, it may vary to the point of becoming absolutely unrecognizable if you're dealing with a massive legacy codebase. But I have to say, I've become a very big fan in the last few years.


It's strange to even call them the same language, it's even stranger when you consider the same compiler will deal with both subsets of the language. It's at the point where someone tells you something is in C++ and you can't even be sure what that means until you look at their code.


I keep my head down in language war discussions, but when people start the usual round of complaints and ridicule of C++, this is exactly what I think: I'm not sure we're actually talking about the same language.


I believe even with C++98 you could have used boost::shared_ptr and boost::scoped_ptr. The addition to the STL effectively didn't change that much. It's just that when you used C++ back then, they probably weren't considered best practice yet.

More important in my opinion are changes to the language, like lambdas or auto.


Why weren't you using smart pointers before c++11? boost had them, and rolling your own is very easy.

I did the latter for a project that started around 2000 and never looked back.


I think the answer to that is that I wasn't an experienced enough developer at the time to know how to do that. ;)


It's still terribly verbose. C++17 barely started to get templates under control. Look at how many characters you need to say "if" and "true"


Not sure how to understand your concern. Yes, it's not 05AB1E. Yes, namespaces are necessary if you have code bases with millions of LOC. But for the most part, C++ is as verbose as you make your type and variable names.

Do you actually find it verbose to read or also verbose to type? I don't consider the latter an issue since 1. you normally spend much more time thinking than typing while programming, and 2. code is read much more often than it is written.


Generally good things, but I get triggered when I hear people call metaprogramming "fun." Such is the beginning of all sorts of woes.


Many of the improvements are to make metaprogramming increasingly unnecessary. I practically don't use it at all anymore.

But coding on C++ has become quite fun. Generic lambdas deserve much of the credit.


Some more:

* Hash maps in the standard library (every so often I have to go and look it up to remind myself that no, I’m not imagining things, C++03 really didn’t have hash maps in the standard library, as ludicrous as that sounds)

* Regexes

* Option and Variant types

* Move semantics

And my personal favorite...

* foo<bar<quux>> is no longer a syntax error


C++ doesn't like making arbitrary choices in standard libraries, and there are lots of performance tradeoffs and API shape choices to make in a language like C++ that refuses to make programers pay for anything they don't want.


I don't know if you mean formatting, in which case I don't believe anything has changed (pretty much anything goes) but yes, you can now do for (auto [x, y] : list_of_coordinates()) ... and things like that.


* is now deprecated.


This is not even remotely true. Let's say you have:

  std::unique_ptr<foo> m_foo;
How do you pass that into a function that can receive a pointer to foo, but does not require it? The only way to do so is to declare the function to take a foo* and pass it m_foo.get()

In summary, * will never be deprecated until C++ has support for something along the lines of Rust's borrowing and lifetimes.


I can't edit my original comment anymore, so I'll just write a clarification in the reply.

Many people have pointed out that C++17 has std::optional. While that's true, I don't think it'll deprecate raw pointers. I'll try to explain why.

Here's an example you can paste, compile and run:

  #include <iostream>
  #include <memory>

  struct foo
  {
    int thingamajig;
  };
  
  void borrow_optional_foo(foo * borrowed)
  {
    if (borrowed != nullptr)
    {
      borrowed->thingamajig = 42;
    }
  }

  int main()
  {
    std::unique_ptr<foo> owned = std::make_unique<foo>();
    owned->thingamajig = 17;
    std::cout << owned->thingamajig << std::endl;
    borrow_optional_foo(owned.get());
    std::cout << owned->thingamajig << std::endl;
    return 0;
  }
If you run it, it should print:

  17
  42
What would it look like if we wanted to use std::optional and get the same behavior?

  #include <iostream>
  #include <functional>
  #include <memory>
  #include <optional>

  struct foo
  {
    int thingamajig;
  };
  
  void borrow_optional_foo(std::optional<std::reference_wrapper<foo>> borrowed)
  {
    if (borrowed)
    {
      borrowed->get().thingamajig = 42;
    }
  }

  int main()
  {
    std::unique_ptr<foo> owned = std::make_unique<foo>();
    owned->thingamajig = 17;
    std::cout << owned->thingamajig << std::endl;
    borrow_optional_foo(std::make_optional(std::ref(*(owned.get()))));
    std::cout << owned->thingamajig << std::endl;
    return 0;
  }
As you can see, there's a tradeoff involved. On the one hand, you get crystal clear, descriptive type: std::optional<std::reference_wrapper<foo>> is clearly an optional reference to foo.

On the other hand, using it is absolutely atrocious: you have to write borrowed->get().thingamajig as opposed to borrowed->thingamajig, and std::make_optional(std::ref(*(owned.get()))) as opposed to owned.get()

Does it work? Absolutely. Is it crystal clear? Indisputably so. Will it deprecate raw pointers? I really, really doubt it, but that's just my opinion.


This is a great reply and I think it echoes what I've seen written elsewhere. For example, this one by Herb Sutter:

https://herbsutter.com/2013/06/05/gotw-91-solution-smart-poi...


> How do you pass that into a function that can receive a pointer to foo, but does not require it?

Same as you would have a function that can receive an `int` but does not require it. Or a receive a `std::vector` but does not require it.

I surmise that you mean "how do you pass any value (pointer or otherwise) into a function that can receive a value but does require it". And I surmise you have before used pointer indirection to pass that value because it has a conventional sentinel value of 0/NULL/nullptr.

You are correct in that optional values did not have a standardized solution, until C++17 std::optional.

If you don't have C++17, I recommend one of the equivalent third-party implementations:

* https://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html...

* https://github.com/akrzemi1/Optional


std::optional does not support references (std::optional<MyVeryLargeType&>) however unlike boost, so this precludes usage with non-copyable types.


That was decided against for now, though I'm not sure why. [1]

In any case, you can use std::optional<std::reference_wrapper<MyVeryLargeType>>.

[1] https://stackoverflow.com/a/26895581


> In any case, you can use std::optional<std::reference_wrapper<MyVeryLargeType>>.

I would frankly give a negative code review to anyone who would do that.

You go from e.g.

    void my_function(T* foo, T* bar, int baz)
    {
      // ...
      foo->stuff(bar, baz);
    }
to

    void my_function(std::optional<std::reference_wrapper<T>> foo, std::optional<std::reference_wrapper<T>> bar, int baz)
    {
      // ...
      foo->get().call(&bar->get(), z);
    }
this also has more overhead (sizeof(std::optional<std::reference_wrapper<T>>) is twice the size of T* ), and let's not even start talking about calling conventions and the compile time cost of having to include both <functional> and <optional> everywhere.


It's hard to tell, but if T* foo can be NULL (and I assume that it can, though there's no visible indication), your first function is gonna segfault.

It's easy to tell that in the second function.

Second function makes for a better code review.


heh, to say that I originally wrote if(foo) and then replaced it by //... because that was not the point. Also, dereferencing an unset optional is also UB, so it would segfault all the same given the same preconditions.


Yes. The code is equivalent, but it's clear that the second is meant to optional.

The first has no such indication, except hopefully some human-readable comment that the pointer may be NULL.


> The first has no such indication, except hopefully some human-readable comment that the pointer may be NULL.

I don't understand. If the parameter was not meant to be sometimes null it would be a reference; not a pointer. The fact that a pointer is used is the "this may be null" indication.


I am not sure I understand what you mean, but now (c++17) you can use an optional in this case.


This is precisely what std::optional is for.


Not formally, but as you were responding to a comment on style: indeed, you don't really need * that often; there are more powerful, safer options in most cases.

But C++ is a systems programming language and * is still quite useful.


  > I had the luxury of starting from an empty buffer
Let's assume that I'm willing to flush the cache. Are there and C++17 learning resources that you might recommend? My day-to-day languages are Python and scripting languages.


Cppreference (learn by doing and look stuff up as you go)

https://cppreference.com

The ISO C++ Standard draft (unironically—it's actually quite readable and is actually up-to-date unlike a lot of books)

https://wg21.link/std

Tour of C++ (book) by Bjarne Stroustrup (start here, you can read it in one weekend)


Not sure I'd recommend the C++ standard for learning the language for a couple of reasons:

* It's gosh darn huge

* While not the hardest standard to read, it is still a standard, so has to be focused on really really low level detains and precise complete explanations instead of being a good learning resource

That said it is a great resource if you're ever confused about some corner of the language -- a lot of articles explain things slightly incorrectly.


For me, pls see my comment https://news.ycombinator.com/item?id=21456353

Also the books by the person whose blog spawned this comment thread seem to be pretty good.


I have seen comments that suggest you can write cross-platform applications (or at least shared business logic) in c++.

Is that the case? If so, how would you recommend someone go about learning c++?


As in: recompile and works? For sure, see for example a lot of the HEP/NP software which runs typically on Linux, BSDs (incl. MacOS) and I think even Windows.

As in: have the same binary? You could probably use the same object files, but shared libraries and executables probably won't work (without trickery), just because the file format is different.


I read Stroustrup's "tour of C++" then looked specifically for blogs that talked about C++ 17. And wrote a lot of code with all warnings enabled.

Yes you can write cross platform applications though perhaps only the engine, not UI.


Qt is great for cross platform UI, for Mac/Win/Linux at least


Last time I checked QT depended on a signal/slot mechanism that was not C++ compatible. There was a code generator.

Has this changed?


If you use CMake you don't see that generator. It automagically works.


No. But there are alternatives to moc like: https://github.com/woboq/verdigris .


Thank you. Very interesting work.


> Last time I checked QT depended on a signal/slot mechanism that was not C++ compatible.

what do you mean by "not C++ compatible" ? Qt is 100% C++ code. The few macros you see (signals:, slots:, emit) are #defined to nothing or public: / private: in a Qt header




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: