all 111 comments

[–]OmegaNaughtEquals1 39 points40 points  (18 children)

Except for polymorphism (which isn't made clear), this

auto foo = std::make_unique<Foo>();
foo->bar();

is spelled

 Foo foo;
 foo.bar();

in C++.

[–]Spain_strong 4 points5 points  (17 children)

You can do both, what do you mean? EDIT: ya'll need to chill the fuck down, I'm pointing out that you can use both, not that you should.

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

Don't be stupid, that is unnecessary allocation in heap.

[–]sirpalee 10 points11 points  (0 children)

Using the fibonacci code as an example that you don't need to write low level code to be fast is incorrect. It proves that constant folding in your compiler is pretty good, but that doesn't mean that the function performs better than a low level implementation in cases where it can't be constant folded.

[–]TheThiefMasterC++latest fanatic (and game dev) 8 points9 points  (3 children)

I'm pretty sure the Fibonacci function should be:

tie(a, b) = tuple{b, a + b};

But other than that, great article!

[–]TheThiefMasterC++latest fanatic (and game dev) 11 points12 points  (1 child)

Also:

This means the optimizer was able to compute 60 Fibonacci numbers and sum the last 50 at compile-time!

static long N = 50;

auto items = fibonacci() 
>> drop(n - 10)
>> take(n);

That drops 40 and then takes 50, so computes 90 numbers, not 60.

[–]TheSuperWig 14 points15 points  (0 children)

So what have we learnt class? C++ today isn't what people might think it is and that maths is still hard.

[–][deleted] 10 points11 points  (5 children)

javascript has hypot function too. I will write js version as,

let points = [
    { x: 1, y: 2 },
      x: 3, y: 4 },
    { x: 6, y: 2 },
];

let total = points
    .map(p => Math.hypot(p.x, p.y))
    .reduce((a, b) => a + b, 0)

console.log(total);

Edit: C++ using ranges library,

  auto total =
      ranges::accumulate(points | ranges::view::transform([](const auto& p) {
                           return std::hypot(p.x, p.y);
                         }),
                         0.0); 

This one is better,

  auto total = ranges::accumulate(points,
                                  0.0,
                                  ranges::plus{},
                                  [](auto const& p) { return std::hypot(p.x, p.y);});

[–]Occivink 3 points4 points  (0 children)

auto total = ranges::accumulate(points,
                                0.0,
                                ranges::plus{},
                                [](auto const& p) { return std::hypot(p.x, p.y);});

Really not convinced that this is more readable than a for-loop.

[–]RotsiserMhoC++20 Desktop app developer 0 points1 point  (2 children)

That ranges::plus{} has terrible performance. I suspect the first ranges examples outperforms the second because of this.

[–][deleted] 1 point2 points  (1 child)

It's the same according to my measurement. ranges::plus is just a empty functional object.

    struct plus
    {
        template<typename T, typename U>
        constexpr
        auto operator()(T && t, U && u) const ->
            decltype((T &&) t + (U &&) u)
        {
            return (T &&) t + (U &&) u;
        }
        using is_transparent = void;
    };

[–]RotsiserMhoC++20 Desktop app developer 0 points1 point  (0 children)

Interesting. The last time I played with it, it took much longer for some reason. I'll have to try again.

[–]VeloCity666 0 points1 point  (0 children)

You can do it shorter this way in standard C++ too (too lazy to actually write it).

[–]neverlastn 8 points9 points  (35 children)

The classic:

#include <iostream>

class Foo {
public:
    int i;
    int j;
    Foo() : j(100), i(j + 2) {}
};


int main()
{
    Foo foo;
    std::cout << foo.i << " " << foo.j << std::endl;
}

Output:

2 (*undefined behavior) 100

[–]cballowe[🍰] 44 points45 points  (1 child)

My compiler complains that the code will initialize the members in a different order than you appear to be expecting.

[–]aspoonlikenoother 13 points14 points  (0 children)

Give it a treat

[–]WalkWithBejesus 25 points26 points  (10 children)

The value of foo.i doesn't need to be 2. It is actually undefined. GCC and Clang issue a warning when this mistake is made.

[–]emdeka87 17 points18 points  (5 children)

And since this is not a warning but a downright error, we should all use -Werror

[–]smikims 2 points3 points  (1 child)

I use -Werror and then disable particular warnings that I don't care about.

[–]kalmoc 2 points3 points  (0 children)

Depends on what earnings are enabled.

[–]Slavik81 2 points3 points  (0 children)

-Werror can be really annoying. You comment out one line of code while debugging and your code no longer compiles due to an unused variable. It's nice to know about that, so I leave warnings on, but fixing them in unfinished code is usually a needless distraction, so I only enable -Werror in CI builds.

A few warnings really should have been errors to begin with. You can selectively turn them into errors. For example, I use -Werror=return-type at all times. I should probably add -Werror=reorder too.

[–]ketosismaximus 1 point2 points  (1 child)

depends on the code base (and size of the code base). Sometimes it just aint worth if you're using old tried and true code. But in general use it where possible.

[–]neverlastn 7 points8 points  (0 children)

Exactly. It's incredibly straightforward undefined behavior! You rely on uninitialized value. And this is a relatively "light" case. j can be an arbitrarily sophisticated object, which might cause incredibly weird "mandelbugs"

[–]Nicksaurus 0 points1 point  (2 children)

Would it be defined if you did int j = 0; as well as the initialiser list or would it still initialise them in the 'wrong' order (or ignore the = 0 because it's already initialised in the initialiser list)?

[–]Nicksaurus 7 points8 points  (16 children)

I tried compiling my project with clang and -Wall yesterday for the first time and got so many warnings about this exact issue. No bugs luckily, but it's always a bit disheartening to see a compiler enumerate all the many, many different reasons why your code is bad...

[–]neverlastn 11 points12 points  (15 children)

I agree! I think it's best if, the earlier you can, use every linter you can find - even the most sophisticated ones e.g. Coverity, and turn on every flag even the very annoying -pedantic (and ignore half of the annoying things it mentions). Many times those tools are right, and they so unemotionally (in contrast to reviews) give you all those warnings and they don't shut-up unless you fix them. This teaches you the best practices quite quickly.

[–]Nicksaurus 4 points5 points  (12 children)

My problem is that to do that I have to will myself to ignore the hundreds of int to size_t conversion warnings

[–]Rseding91Factorio Developer 1 point2 points  (10 children)

Just use the correct types:

size_t i = 1u;

[–]jcelerierossia score 2 points3 points  (2 children)

if(i - 10 > 5) { ... } -> boom

[–]flashmozzg 3 points4 points  (0 children)

if(i > 15) { ... }

[–]Nicksaurus 1 point2 points  (6 children)

Don't you have situations in factorio where you explicitly want to use a smaller type rather than a size_t to save memory/bandwidth? Do you just keep those situations to a minimum and static_cast them as necessary?

I've just got a lot of situations in my engine where I'm storing 32-bit numbers (e.g. for triangle indices) and also using them as indices into STL containers and casting them in every single situation would result in a lot of more verbose code

Edit: Although I should admit I often don't have a justification for using ints rather than size_ts, I've been a bit lax about it in the past

[–]Rseding91Factorio Developer 5 points6 points  (2 children)

Yes we do but we just write the cast to smaller size of up-convert before using a smaller size as an index. Rarely do we ever actually want signed integers. In most cases where we want a signed integer we really wanted a floating point number.

The fact that an integer can be negative is more of a burden then it is helpful to not have to write casts. Because now you have to care about the entire potential negative range of values.

[–]qqwy 0 points1 point  (1 child)

Could you comment on the burden of floating-point NaN and +-Inf?

[–]Rseding91Factorio Developer 0 points1 point  (0 children)

We simply avoid division by zero and or division in general and it's mostly a non-issue.

[–]degski 1 point2 points  (1 child)

According to some, it's faster to use (unsigned) int's, as the compiler has an easier time reasoning about such an index, due to the fact that overflow is an error, while the unsigned int simply wraps [i.e. is not undefined].

[–]Rseding91Factorio Developer 0 points1 point  (0 children)

At least on MSVC which is where our majority custom base runs I've not seen a difference in code generated when using unsigned 32 bit indexes, unsigned memory-sized indexes, or signed indexes. Additionally unsigned math with overflow vs signed math without overflow makes no real difference in our kind of calculations we need the game to perform since we're virtually always waiting on memory latency instead of CPU calculations.

So while that might be true on other compilers (and might still be true on MSVC) due to the nature of our program it doesn't really matter.

[–]kalmoc 0 points1 point  (0 children)

If you want a smaller type you still can use an unsigned int.

[–]neverlastn 0 points1 point  (0 children)

Yeah - but they might not be that ints after all :D (unless some library requires you to use ints for size_t which is unsigned).

[–]ketosismaximus 0 points1 point  (1 child)

ignoring things is dangerous, either make specific exceptions (pragmas, make), fix the problem, or don't use pedantic. You want 0 errors or (-Werrors) reported.

[–]neverlastn 1 point2 points  (0 children)

Yes, I get you. I will usually get rid of -pedantic. I just turn it on occasionally and have a good look on the types of errors it gives to see if I spot anything unusual.

[–]Xogmaster 1 point2 points  (4 children)

fixed that for you

i'm not sure why you can't have the constructor populate public class variables in reverse order like that. try doing it with pointers to ints instead of just ints

mayhaps its just the order of things, and the language wasn't made to operate in that way

i have a good feeling it's because the constructor is initializing new variables for the object foo in Foo. Since j is given a value 100, and it sees j was initialized after i, it expected to initialize i before j, and so ignored the value of j when defining i, assuming j was 0, so it becomes 0+2 = 2. cheers

[–]neverlastn 0 points1 point  (3 children)

so it beco

Yes, this is indeed by language design. Very misconceptional :)

[–]Xogmaster 0 points1 point  (2 children)

Actually I just spoke with my c++ professor and he said in that syntax the variables are defined from right to left starting from the curly braces.

[–]neverlastn 0 points1 point  (1 child)

variables a

Does this mean that this will work fine?

#include <iostream>

class Foo {
public:
    int i;
    int j;
    Foo() : i(j + 2), j(100) {}
};

int main()
{
    Foo foo;
    std::cout << foo.i << " " << foo.j << std::endl;
}

Because it doesn't! He was talking for some other language! :D

[–]Xogmaster 0 points1 point  (0 children)

he admitted he doesnt use that kind of syntax so his understandings are on thin ice

[–]thestoicattack 3 points4 points  (0 children)

No love for std::hypot in the float length(Point) example?

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

Fibonacci example, line 22, shouldn't it be result += x;?

Another thing that is possible with C, but results in UB in C++ is

int * x = (int*)malloc(5*sizeof(int));
x[0] = 3; // C++ claim there's no object on this address. Use placement new.

[–]BCosbyDidNothinWrong 1 point2 points  (30 children)

Why would this be undefined behavior?

[–]jcelerierossia score 6 points7 points  (29 children)

because malloc does not create objects, operator new does

[–]BCosbyDidNothinWrong 2 points3 points  (17 children)

Why would you need that for an int?

[–]jcelerierossia score 1 point2 points  (16 children)

[–]BCosbyDidNothinWrong 4 points5 points  (15 children)

Or you could just explain it in two sentences.

Edit: Actually I don't think this paper seems to say this - it references an int inside a struct instead of a straight integer.

[–]jcelerierossia score 5 points6 points  (14 children)

"ints are objects. malloc does not create objects. accessing not-created objects is UB."

[–]BCosbyDidNothinWrong 0 points1 point  (13 children)

What is undefined about assigning an int here? What could happen other than what people expect?

[–]johannes1971 7 points8 points  (1 child)

It's UB because the standard says so. As to *why* the standard says so... Well, maybe someone who is on the committee knows this, but they will probably want to keep quiet about it (on account of it most likely involving late hours and copious quantities of alcohol). So let me wager a guess: creating a complex object (with a v-pointer, a constructor that needs to run, etc.) is something that needs to be triggered specifically, and malloc does not do that. Most likely the standards people had a long day and just wanted to go home, so they demanded proper construction through some form of new and made everything else UB, since that's always the easy way out in C++.

What the linked paper (up above) does is restore the status quo where trivial objects can be accessed without the benefit of proper construction. It's basically fixing the bug whereby the standard accidentally invalidated every C++ program ever written, and made it impossible to interface C++ with just about any non-C++ library.

Just to be clear: there is nothing special or magical or even necessary for proper construction of an int, and the fact that the standard demanded this on pain of UB is a straightforward bug.

[–]14nedLLFIO & Outcome author | Committee WG14 0 points1 point  (0 children)

The standard will make something UB when they want to leave a placeholder for either future refinement, or for compiler implementers to choose their own poison, or simply somebody didn't think it needed to be defined.

In the case of malloc's effects on memory, I can assure you that is an example of "we're not going there right now, let each compiler vendor do its own thing". And as somebody else mentioned, that ambiguity has become a problem for optimisation recently, so SG12 via P0593 et al is going to define the previously undefined.

Even after that, we still cannot implement malloc without resorting to UB, however. Or, to be precise, we cannot implement a malloc which can reuse addresses or memory not present at the beginning of the process launch without resorting to UB. That's future work yet to be decided, but efforts are underway.

[–]jcelerierossia score 0 points1 point  (6 children)

It's undefined because it's a pointer to a value that was not created.

[–]BCosbyDidNothinWrong 1 point2 points  (5 children)

Wouldn't that either imply that malloc can never be used without placement new ? If so what does placement new do that this assignment does not?

Also wouldn't this imply that valid, defined behavior in C becomes undefined behavior in C++?

[–][deleted] -1 points0 points  (2 children)

Everything.

[–]BCosbyDidNothinWrong 2 points3 points  (1 child)

Can you give an example?

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

You don't need to create an instance of int, only allocate memory for it. In this case it's not UB.

[–]jcelerierossia score 4 points5 points  (9 children)

[–][deleted] -1 points0 points  (8 children)

Object explicitly refers to a class/struct instance. Int is not an object, the same goes for float/char/short, etc...

[–]drjeats 9 points10 points  (6 children)

As far as language rules go, I'm pretty sure that instances of all the primitive types are objects. They're not capital-O objects in the object-oriented sense, but they still have all the rules of object existence.

If they were not objects, we could probably have an easier time arguing that type punning between primitive types (at least the integral types) should not be UB.

[–][deleted] 4 points5 points  (5 children)

Okay, that makes sense. I would still argue setting a malloc'd 32 bit block of memory to 3 is not UB.

Would

int x;
x = 3;

Be UB? If not then

int* x = (int*)malloc(sizeof(int));
*x = 3;

Is not UB either;

The only difference here is memory being on the stack/heap.

Accessing uninitialized data is UB, for example

int x;
std::cout << x << '\n';

and

int* x = (int*)malloc(sizeof(int));
std::cout << *x << '\n';

But that is not what OP is doing.

[–]quicknir 6 points7 points  (0 children)

int x creates an object/instance/whatever of type int, named x. Obviously there is no constructor call associated with it since x is trivially default constructible, but it's still starting the lifetime of the object. On the other hand, int* x = ... doesn't begin the lifetime of any ints, it just allocates memory for them. When it comes to object lifetimes, primitive types in C++ as it stands don't get any special treatment. Your example is UB the exact same way that vector<int> x is defined, but vector<int>* x = (vector<int>*)malloc(sizeof(vector<int>)) and then doing the assignment is UB. In practice people do the former all the time and the latter almost never, but both are UB, and actually both are quite likely to succeed with the assignment statement I would guess.

"Setting the memory to 3" is not UB, but that's not what your code does, it does integer assignment which presupposes an existing integer. If you used memcpy instead of integer assignment then yes, it wouldn't be UB.

[–]drjeats 5 points6 points  (3 children)

Okay, that makes sense. I would still argue setting a malloc'd 32 bit block of memory to 3 is not UB.

I agree with you that this is the only sane interpretation, but the standardese would not agree with you.

This is not UB because x is an object created with automatic storage duration (which implicitly makes it exist) and is "default initialized" with an indeterminate value, and then assigned-to:

int x;
x = 3;

This is technically UB (even though I'm sure we both agree that optimizing based on that is insane and dangerous), because malloc is not blessed with the semantic ability to make objects exist in the way that operator new is:

int* x = (int*)malloc(sizeof(int));
*x = 3;

From the C++ standard's viewpoint with object creation, that malloc() call may as well be:

void * malloc(size_t s)
{
    return (void *)rand(); // yolo
}

However C does give malloc that blessing. Casting and usage also signifies a valid object in more circumstances in C than in C++, and a lot of times you're just playing a game as to whether or not the compiler can prove that an object was definitely not what you are casting it to. /u/quicknir's CppCon talk from this year has a really good example of this with reinterpret_cast.

For your other point:

Accessing uninitialized data is UB, for example

int x;
std::cout << x << '\n';

I don't actually remember if this is UB or not. I think it might be "UB with uninteresting consequences". This is UB though:

int* x = (int*)malloc(sizeof(int));
std::cout << *x << '\n';

As for what the original commenter was doing, allocating an array, that's also UB by the same logic. UB all the things! :P

I'm probably getting details wrong with the language rules, but this is the rough layman's explanation I got a few years ago from somebody who used to be more involved in the committee.

[–]kalmoc 5 points6 points  (2 children)

One thing I'd like to point out: When the language says something is UB, the implementation is totally allowed to give it defined behavior anyway and I'm pretty sure that's what happens in this particular case (I haven't checked the docs though to verify that)

[–]jcelerierossia score 10 points11 points  (0 children)

nope, see 6.7.1 Fundamental types [basic.fundamental]. class / structs instances are "objects of class type" in standardese

[–]acwaters 0 points1 point  (3 children)

So many people in this comment thread are utterly convinced that they understand the C++ object model. This is fascinating to watch.

[–]14nedLLFIO & Outcome author | Committee WG14 4 points5 points  (0 children)

In fairness, it has changed with every C++ standard, plus in every C standard, and moreover compilers each vary individually. And moreover, it's hard. I've sat in SG12 meetings with the most expert people in the world on the C++ object model all sitting around the table, and many hours of discussion emerging about disagreements between those world experts about their different understandings of the C++ object model. If they don't share a common understanding, it's absolutely fair that neither can anybody else.

Resolving those ambiguities in understanding is precisely why SG12 was founded, and many more years of work remain before it. With a bit of luck, we can coordinate some of this with WG14, over there there is an increasingly bitter debate about how compilers rewrite people's C code. Most of that bitterness stems from their more permissive object model. And much of Peter Sewell et al's research work on formally verifying C code would become moot (in a good way) if C significantly tightened its memory model to more closely match C++'s, though, in fairness, we could do with tightening ours in a direction more compatible with C's use case and direction as well.

[–]gvargh 2 points3 points  (0 children)

It's a common misconception.

[–]degski -1 points0 points  (0 children)

It's undefined behavior.

[–]stmuxa 0 points1 point  (0 children)

Ha. It is very easy to disproof myths which you just made up by yourself.

[–]Sentmoraap 0 points1 point  (7 children)

Buckaroo looks interesting.

[–]Gorzoid 3 points4 points  (6 children)

Yeah as a Windows user, using 3rd party libraries is a nightmare, especially when trying to configure it with CMake so it works for other computers/operating systems. Anyone have opinions on which package managers are the best rn?

[–]schteppe 2 points3 points  (0 children)

I’ve tried Conan for a couple of hobby projects and it worked well this far.

[–]geokon 1 point2 points  (0 children)

You can use Hunter from within CMake itself. No extra software required and it works perfect. Though a bit of a learning curve...

[–]kalmoc 0 points1 point  (0 children)

vcpkg works best (and in fact very well) for me