top:
day week month all

C

Community for : 4 weeks

Matters concerning and comparisons involving C or C++, the workhorse of performant and efficient computing for over 50 years.

No AI slop posts.

Owner: SithEmpire

Mods:
SithEmpire












2
C++: Operator overloads, explained using nigger math     (pomf2.lain.la)
submitted by SithEmpire to C 2 weeks ago (+2/-0)
2 comments last comment...
https://pomf2.lain.la/f/tgq4c86w.png

C++ gives the programmer extremely broad power over data types and how they behave. You can create a custom type and define whether and how it can be added, multiplied, compared, iterated, printed as output, called as a function, transformed into another type—all of which then interacts with the C++ language and enables its use elsewhere.

Developers of other languages whine and cope about this, notably Java maintainers calling it too confusing to read code where custom types can be added together; it's nonsense at best, if not an insult in assuming people aren't intelligent enough for that feature. A type representing a 3D vector can have a perfectly clear meaning associated with adding them together. As usual, to stand against that because it could be abused is yet another case of trying to protect programmers from themselves, a mistake which so many maintainers just cannot resist making.

That is, especially when abusing it is such a good way to demonstrate it! Suppose we are creating a new type to represent integers for nigger math, which I'll call "nigint" for nigger integer. We can start by making a "wrapper" which stores a normal integer:

class nigint
{
private:
int i;

public:
nigint(int i=0) : i(i) {}

};

The private section stores data and functions only to be accessible internally, so outside interaction will be forced to work via definitions we'll add to the public section. That public function is a constructor which stores a normal int directly, and has an implicit return-type (a finished object of our nigint type). The default value of zero allows for omitting it, in effect defining two constructors with and without the parameter, which has various compatibility advantages. The braces are an empty constructor body, because there isn't anything else to do and it cannot be absent.

Good so far, albeit lacking interaction and functionality, so code won't yet compile if it attempts to use or output one of these. At minimum, we can tell the compiler that our type can at least be cast to an integer by defining the corresponding operator, with this placed in the public section:

operator int() const {return i;}

The keyword operator is used for defining functions which interact with the language. Cast-operations are special in also having an implicit return type, but otherwise the syntax is just like a normal function, including const to indicate that the function does not modify our object.

A quick test before continuing:

#include <iostream>

class nigint
{
private:
int i;

public:
nigint(int i=0) : i(i) {}
operator int() const {return i;}
};

int main(int, char✱✱)
{
nigint ni(4);
std::cout << ni << "\n";
}

That at least outputs "4" rather than failing to compile. Actually, commenting out the operator definition can be illustrative here, it causes this compile error:

no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘nigint’)

Ultimately the iostream interface wants a way to output a nigint using the << syntax, but it's happy without that as long as it has an implicit way available, which the cast to int provides.

Niggers don't know how to write or speak proper numbers though, so let's actually implement that operator to control directly what it does for our type. We need a function header for operator<< which takes references to a std::ostream and a nigint, and returns the std::ostream reference back. We also need access to the underlying int though, so first declare this function within the nigint class as a "friend":

friend std::ostream& operator<< (std::ostream& os, const nigint& ni);

This is a deliberate mechanism to give that function access to the private members within our type, in this case the underlying int value. Now we can implement it outside the class, using full access:

std::ostream& operator<< (std::ostream& os, const nigint& ni)
{
switch (ni.i)
{
case 0: os << "no"; break;
case 1: os << "wuh"; break;
case 2: os << "tu"; break;
case 3: os << "tree"; break;
case 4: os << "fo"; break;
case 5: os << "fav"; break;
case 6: os << "seek"; break;
case 7: os << "sen"; break;
case 8: os << "aid"; break;
case 9: os << "nan"; break;
default: os << "sheeeit";
}
return os;
}

Placing that just after the nigint class, the previous program now outputs "fo". The return reference there is just part of how iostream works, allowing multiple << operations to be chained together. Note that the attempt to access ni.i only works because the function was declared as a friend, otherwise the compiler error will say private within this context.

Now we can use the type as a number and impose that output logic:

int main(int, char✱✱)
{
nigint ni(4);
std::cout << ni << "\n";
std::cout << nigint(ni + 3) << "\n";
}

Output is "fo" and "sen", as expected. The expression ni + 3 is using the implicit cast to int, for which the result will be another int and the normal output would be "7", but we're telling the compiler to make a temporary nigint instance of it first before the output stream gets to see it.

Niggers also can't add properly though. No problem; we can also define what happens when we add to a nigint object. This we can do entirely within the nigint class:

nigint operator+ (int x) {return i + x + 1;}

In this case, the object itself is the value to the left when the operator is used, and the argument x is the value to the right. The return expression is just three normal integers added together, which the compiler casts implicitly to nigint due to the function return type, and it knows how to do that because the correct constructor exists.

Because that operation returns a nigint, we can now also test the summation directly without the explicit constructor:

int main(int, char✱✱)
{
nigint ni(4);
std::cout << ni << "\n";
std::cout << (ni + 3) << "\n";
}

Now we get "fo" and "aid", great. Some niggers know at least a few of the tens-place numbers though, let's add those into the operator<< overload. We can check for some known cases and output those words first:

std::ostream& operator<< (std::ostream& os, const nigint& ni)
{
int i = ni.i;
if (i >= 40 && i < 50) {os << "fody"; i -= 40;}
else if (i >= 50 && i < 60) {os << "fiddy"; i -= 50;}

switch (i)
{
case 0: os << "no"; break;
case 1: os << "wuh"; break;
case 2: os << "tu"; break;
case 3: os << "tree"; break;
case 4: os << "fo"; break;
case 5: os << "fav"; break;
case 6: os << "seek"; break;
case 7: os << "sen"; break;
case 8: os << "aid"; break;
case 9: os << "nan"; break;
default: os << "sheeeit";
}
return os;
}

This is where the const protection is good to keep in mind; the operator is not allowed to modify the object being printed, but it can use that read-only access to make a mutable copy of the integer. That then lets us subtract off any part of it accounted first before handing the remainder to the switch statement.

Test:

int main(int, char✱✱)
{
nigint ni(4);
std::cout << ni << "\n";
std::cout << (ni + 40) << "\n";
std::cout << nigint(53) << "\n";
std::cout << (ni + ni + ni) << "\n";
}

Output:

fo
fodyfav
fiddytree
sheeeit

If there's one way that nigger math is useful, it's being an example!
18
C++: Computing for the discerning gentleman     (C)
submitted by SithEmpire to C 4 weeks ago (+18/-0)
25 comments last comment...
In part to combat the wave of junk articles crapped out by generative AI with examples using the most faggy language (python) ever designed, I decided to start showcasing various aspects of C++, easily my preferred platform.

It has its oversights and shortcomings I'll also present, but generally C++ has a respectable, formal style which helps the compiler produce performant and efficient binaries while also exposing the abject peasantry of various other languages once you get to know it. Exactly as human language was more well-spoken decades ago, so too was computing language, to the extent that using other platforms seems like writing foul-mouthed vulgarity in comparison.

This is also sort of a test-post to see how well the site formatting system handles code (I tried it in a message to myself but the formatting doesn't work there).

`
#include <iostream>

int main(int, char**)
{
std::cout << "The iostream syntax is weird, but allows for some compile-time checks and runtime safety.\n";
std::cout << "Terry Davis was right about it being niggery compared to printf though.\n";
return 0;
}
`

If the code delimiters don't work, it'll probably interpret the pointer asterisks as bold text, or perhaps the include directive as a HTML header level.

If the `code` doesn't work, it's because you have a messed-up C++ compiler resulting from another attempt at Embrace-Extend-Extinguish. For sake of argument I'm using GCC g++, but in general a compliant compiler ought not require preprocessor directives specific to itself.
13
C++: When efficient operations and the language itself are one and the same     (pomf2.lain.la)
submitted by SithEmpire to C 4 weeks ago (+13/-0)
12 comments last comment...
https://pomf2.lain.la/f/819ag2cg.png

The linked image has formatting as intended originally; for the post text I had to use alternative characters to avoid triggering the site formatting, which REALLY needs a proper pre-formatted code feature.

Where most other languages have rules about passing primitive types by value and objects by reference, C/C++ builds subtle control of that into its language. This is no simple quirk; passing by value is also known as copying, and that comes with a real performance cost, such that learning a language which controls it is the nature of achieving good performance in and of itself.

Consider a simple and fairly useless class representing a square with a width and a height, entirely public to ignore encapsulation for now. We store the width and height, and we provide an area calculation and a function to scale its size:

class Square
{
public:
double width;
double height;

double area()
{
return width ✱ height;
}

void scale(double factor)
{
width ✱= factor;
height ✱= factor;
}

};

This works at least, but neither as optimally nor with as much freedom as it could have.

The freedom point is good to address first; consider how that area function. Despite only reading the class variables without changing them, the entire function will be treated as if it can conceivably change the Square object on which it is called. Due to that, the area function will be inaccessible given a const Square object, even though it makes no changes.

The area function should be specified like this:

double area() const
{
return width ✱ height;
}

Shoving const in the function header makes the object fields read-only within the function—in return for being allowed to call the function on a const version of the object. Use of const is both a means of object protection and also a precursor to compiler optimisations such as avoiding copying and reloads.

Now to be more optimal, consider the scale function. It modifies the object and thus cannot be const, but this is about the scale factor parameter. Calling that function will supply it with a copy of the factor, which can be appropriate if it genuinely needs a temporary copy it can modify without affecting the calling code, but clearly it doesn't change the factor. In one sense, it would be better to accept a reference instead:

void scale(double & factor)
{
width ✱= factor;
height ✱= factor;
}

This bypasses the overhead of copying, although now that function cannot be used with a const double, because it could conceivably change the value (even though in practice it doesn't). This applies to calls with literal number as the factor, such as scale(1.5)—that 1.5 is itself a const double.

The scale function should be specified like this:

void scale(const double & factor)
{
width ✱= factor;
height ✱= factor;
}

Introducing that guarantee now allows literal numbers and any named values (const or otherwise), while also avoiding copying anything.

Even though this example involves only a double weighing in at 8 bytes, that is nonetheless both how to talk to the C/C++ compiler and also how to avoid unnecessary copying. It is not an early optimisation mistake; it is a basic part of the language which should be in continual use, it just so happens that the language and the optimisation are one and the same!
4
C++: Using goto sensibly is fine, and don't let anyone tell you otherwise     (pomf2.lain.la)
submitted by SithEmpire to C 3 weeks ago (+4/-0)
5 comments last comment...
https://pomf2.lain.la/f/m66t312.png

The linked image has formatting as intended originally. These are exposing so many different ways things look wrong without an official code markup tag.

The programming world at large has cast goto statements as always bad, but attempting to protect bad programmers from themselves is a cause so lost that it has six seasons and a 12-minute epilogue. Seeing someone write messy code and taking away everyone's goto is like seeing a nigger waving a handgun around and taking away everyone's handgun, failing to identify the problem either way. We don't need common sense goto control.

All programming constructs and mechanisms can be abused, such that singling out goto is pointless. Using goto can be perfectly appropriate and perfectly elegant. C/C++ has at least one case where goto appears absent a better mechanism, but I'll also show some situations where it's as well to use it.

Suppose we iterate pairs of integers i then j, but sometimes want to skip to the next i, ignoring any remaining j and any operations after the j-loop. One way to do this by jumping out and over said operations by placing a label at the end of the i-loop:

bool avoid(int i, int j);
void process(int i, int j);
void conclude(int i);

for (i=0; i<10; i++)
{
for (j=0; j<10; j++)
{
if (avoid(i, j)) goto next_i;
process(i, j);
}
conclude(i);
next_i: ;
}

This is for lack of labelled break and continue, which has been suggested many times and looks like it could actually be in the next C++ release. Until then, the official C solution is goto, and it is perfectly clear and performant. It is not better to screw around with a flag when you can just jump the flow. Making a separate inner function and returning early can be appropriate though, if it is not too arduous to pass in any local variables needed.

For a different scenario where goto is perfect, suppose we have a system state update function which must consume all the time given to it. Action functions modify the remaining time and return true if they concluded early, potentially having changed the state:

int state;

bool action_1(int✱ time_ms);
bool action_2(int✱ time_ms);
void action_idle();

void update(int time_ms)
{
update_again:
if (state ꞊꞊ 1)
{
if (action_1(&time_ms)) goto update_again;
}
else if (state ꞊꞊ 2)
{
if (action_2(&time_ms)) goto update_again;
}
else action_idle();
}

The goto-avoiding way could be a while loop and incurring an extra test whether any time remains. Probably worse than that is calling itself recursively just for re-entry with a lower amount of time. If the logic is in the position of knowing it needs to consume remaining time, just jump to the top!

Also in the context of state systems, there is nothing wrong with having multiple possible conclusions at the end of a function and a way to jump to the alternative flow, which becomes useful when multiple logic branches use it. Loosely:

{
// Function body with various decisions
// ...
return normal_result;

alt_conclusion:
// Alternative computation
// ...
return alt_result;
}

As with any mechanism, sensible use of it and sensibly-named labels are what makes it pass the readability test. Naming a label "hell" is also funny, everyone should get to do that at least once.