
Ill-Advised C++ Rant, Part 2 (2016) - signa11
http://www.codersnotes.com/notes/cpp-rant-2/
======
stabbles
> Calculating the size of static array

In C++17 you can do

    
    
        #include <array>
    
        std::array myarray{ 4, 5, 6, 7, 8, 9 };
        int my_size = myarray.size(); // returns 6
    

which even deducts the type `std::array<int, 6>` for you.

~~~
mattnewport
You can also use the std::size() free function to get the size of a C array,
an std::array or any other standard container.

[http://en.cppreference.com/w/cpp/iterator/size](http://en.cppreference.com/w/cpp/iterator/size)

~~~
stabbles
A free function for this looks even better IMO.

------
colanderman
> Calculating the size of a static array.
    
    
      #include <type_traits>
      int myarray[] = { 4, 5, 6, 7, 8, 9 };
      int x = std::extent<decltype(foo)>::value;  // returns 6
    

> Converting a string to an enum

Generally a bad idea, as it couples a protocol (whatever is feeding you the
strings) to symbols in your language. Often, one of those has constraints that
the other cannot meet.

> Binary file includes
    
    
      objcopy -I binary -O elf64-x86-64 -B i386:x86-64:intel in.bin out.o
    

Not the prettiest, but, why should a language do the linker's job?

> FourCC codes

How often does this come up, that you can't write a 10-line class to do what
you want? Not to mention the obvious issue of endianness in the example given.
Last time I had to do this I was writing a PNG parser, which was not something
I should have been doing.

> Types as first-class primitives

I've been coding for 25 years and never needed nor wanted this. I know not
everyone agrees, but personally I find reliance on RTTI to be a code smell.
(Granted, pre-C++17 it's kind of forced due to lack of a proper variant type.)

(I realize the above is opinionated, but… so is the OP! :)

~~~
stabbles
OP complains about C-style arrays when criticizing C++. Just using
`std::array` makes life easier.

~~~
sseagull
Yeah that one is a little weird. C++11 included std::array to solve that
problem (among others), but he doesn't use it and then complains about it
anyway.

------
periram
Totally agree with the author. I have started coding again in C++ after a 10
year detour to python.

Can we have basics first, and then robust libraries for say, as simple as
networking.

Batteries included, is a thing, and is something a lot of programmers like me
look for when choosing between languages.

~~~
sseagull
Unfortunately, I have already heard complaints about the size of the C++
standard library, and how "I can write a better vector/list/etc.. class". I
can't imagine what people would think of a networking module, designed by
committee and full of compromises.

I don't envy the members of the standards committee. It is a hard and very
thankless job.

~~~
colanderman
I've been a hardcore C programmer for decades and just got into C++11 this
past year. I _LIKE_ the standard library.† It's pretty much just basic data
layout stuff that I've done 100 times manually in C, nothing more, nothing
less. Leave the complicated data structures and libraries to 3rd parties.
std::vector is already exactly what I want (though it could do with one fewer
specialization ;). There's pretty much one way to implement it and they got it
right.

On the other hand, there's 100 ways to implement, say, an HTTP client, and
they're all wrong.

† Except for iostreams. Ugh.

------
DerekL
One way to solve the do/while(0) problem is by using a “statement expression”.
It's a GCC extension, and I don't know the reason it has never been accepted
into the standard. Here's how it works:

    
    
        #define my_assert(X) ({ if (!(X)) error(); })
    

Another way is to use a lambda expression, and then call it immediately:

    
    
        #define my_assert(X) ( [=]() { if (!(X)) error(); } )()
    

Because the lambda captures by copy, it will catch inadvertent side effects in
some cases.

You can also use the conditional operator in this case:

    
    
        #define my_assert(X) ( (X) ? error() : ((void)0) )

~~~
DerekL
The last one is backwards, should be:

    
    
        #define my_assert(X) ( !(X) ? error() : ((void)0) )

------
LambdaComplex
I think Rust solves a lot of these problems.

> Calculating the size of a static array

some_array.len()

> Getting the maximum value of an enum

Rust's enums are strongly-typed (I think that's the right phrase), so this is
a non-issue.

> Converting an enum to a string

impl Display for MyEnum (and then call to_string() on it)

> Converting a string to an enum

This could be tricky, since Rust enum variants can contain data. But if none
of the variants contain data, it would be easy to make a method that returns a
Result<MyEnum, CustomParseError>

> Binary file includes

include_bytes! macro (or the include_str! macro, depending on the exact
scenario)

> Switch/case is still stupid

Pretty sure Rust's match is a lot better

~~~
pcwalton
> This could be tricky, since Rust enum variants can contain data. But if none
> of the variants contain data, it would be easy to make a method that returns
> a Result<MyEnum, CustomParseError>

Usually when you want to convert a string to an enum, you're trying to build
some kind of parser or deserialization library, in which nom or serde are the
best choices. (Of course, C++ has several parser frameworks too.)

~~~
LambdaComplex
Yeah, I would definitely be using serde in that case

------
banachtarski
This person is not a very good C++ programmer. Also for some of the things he
mentions, it will be covered by the Reflection TS.

1\. Size of a static array: Use std::array please

2\. Maximum value of an enum: See reflection TS

3\. Converting enum to string: ditto

4\. Converting string to enum: ditto

5\. #pragma once: see modules. They _are_ in fact trying to make something
better. There are also some cases where you don't want/need it

6\. C99 designated initializers. They are there!

7\. Binary file includes. Ugh this is just such a dumb idea I can't even. When
do you want the file to load? When the executable does? What if you want to
read it in chunks? This idea is not well suited for a systems-oriented
language

8\. FourCC codes. Again, this is a silly idea not even worth addressing. Just
define it yourself. C++ is a programming language. Not your media toolkit

9\. do/while(0). What are you using macros for in 2016 (the time of writing)?

10\. preprocessor gripes. Stop using it! You have <type_traits> and stuff now

11\. Switch/case is stupid. No it's not. You want fall/through cases as well
and if you can't see why, don't criticize. This is true in pretty much EVERY
language that supports switch/case

12\. Still no strong typedefs. There are! Use the explicit keyword on a
constructor definition.

13\. Types as Primitives. See reflection TS

Basically, this feels like any other "C++ rant." Someone who didn't learn the
language properly or respects what the language needs to achieve.

~~~
pcwalton
> This person is not a very good C++ programmer.

Am I a bad C++ programmer for agreeing with some of it?

> 5\. #pragma once: see modules. They are in fact trying to make something
> better. There are also some cases where you don't want/need it

The fact that modules have taken so long, and still have prominent members of
the community saying they need to be sent back to the drawing board, is a
legitimate criticism.

> 7\. Binary file includes. Ugh this is just such a dumb idea I can't even.

It's a useful feature, much used in the language I work on, allowing you to
distribute a program that needs just one or two static data files as a single
executable. It's also good for packaging data as libraries: for instance,
Unicode libraries need a lot of random tables and it can be convenient to
store these in binary form. Having to embed binary in C/C++ syntax as, say,
ICU does just adds parsing overhead at compile time for no reason.

> 9\. do/while(0). What are you using macros for in 2016 (the time of
> writing)?

There are lots of use cases of macros, too many to list here. Templates don't
always suffice.

> 11\. Switch/case is stupid. No it's not. You want fall/through cases as well
> and if you can't see why, don't criticize. This is true in pretty much EVERY
> language that supports switch/case

I designed much of the pattern matching system in an industry language without
fall through in switch case and I have never missed it. Nor has anyone else in
the community I know of.

~~~
to3m
C# lets you do "goto case XXX" to go to another case, and empty cases fall
through to the next. This is a bit inconsistent, but it at least retains the
useful fall-through case, gets rid of the usually-undesirable case, and still
accommodates the occasional exceptions.

------
dpc_pw
This is kicking a dead horse here. IMO, almost everything about C++ is
terrible in this day and age. I'm so happy Rust exists and is doing well. I'll
do anything in my power to never again create another C++ project.

------
DerekL
You can use user-defined literals for a portable way to write four-character
codes:

    
    
        constexpr std::uint32_t operator "" _4CC(const char* str, size_t len) {
            std::uint32_t result = 0;
            for (size_t i = 0; i < len; ++i) {
                result = (result << 8) + (std::uint32_t)(unsigned char)(str[i]);
            }
            return result;
        }
    
        auto const movie = "MooV"_4CC;

------
andrepd
>Here's an example implementation for you. If you're using Visual C++, there's
a built-in version. They could literally cut-n-paste this one line into
<stddef.h> today.

>#define countof(X) (sizeof(X) / sizeof((X)[0]))

Could they? What if X is empty?

~~~
gruez
an empty struct has a size of 1 byte.

    
    
        struct Widget{  
        };
    
        Widget widgets[256];
        printf("%d %d", sizeof(widgets), sizeof(Widget));
    

prints out

    
    
        256 1
    

if there are 0 elements, there are no issues because it's 0/x = 0.

~~~
wrsh07
the point is that they're indexing into an array of unknown size.

~~~
gruez
indexing in that case doesn't trigger a dereference, since it's being passed
to the sizeof operator.

------
andrepd
>Converting string to/from enum

It's generally a bad idea to do this, and an enormous headache to even
attempt. If you want to do this, maybe you don't want to use an enum, because
that's not what they are for. Maybe you want a hash map instead?

~~~
alkonaut
Why is it a bad idea? And if it's a headache, isn't that exactly when the
language itself should do it so the developer doesn't have to?

~~~
sidlls
How is it a good idea? Not everyone needs this functionality.

I wonder how much effort might be required to implement a purely opt-in
syntax/library for the purpose: something like Ada's Enumeration_IO.

~~~
alkonaut
When you need it the compiler knows you need it. If you have enum Pet { Cat,
Dog } and you write

    
    
        cout<<nameof(Dog)
    

The compiler could just replace it with the string value at compile time.

If you have a dynamic one the compiler could just make an array (or worst case
hash table lookup) into a static array created at compile time. It's fairly
simple and costs nothing if you don't use it. And it's a lot simpler and more
elegant to do through the compiler than through a lib, metaprogramming etc.

And how useful is it? it's at least useful to be part of how most other
languages work these days. An enum is should be seen as a list of names _and_
values.

It shouldn't just be limited to be a glorified way of saying

    
    
        const int Cat = 0;
        const int Dog = 1;
    

Defined like that I'd find it completely _unnatural_ if the symbol names were
available at runtime.

~~~
sidlls
It has a cost in the size of the object file or executable at least. What if I
don't want my data segment being polluted with enum-string-values or, worse,
your secret array/hash-table implementation, regardless of how little extra it
is?

Something like this ought to be opt-in and explicit.

~~~
alkonaut
If you use nameof(Cat) then the string "Cat" is included. If you use
nameof(some_pet) then the array ["Cat", "Dog"] is included. It's zero cost for
something you don't use and it's exactly the same cost as you would have paid
by manually adding this.

~~~
sidlls
Ah, right, I see: the compiler only adds these things if it detects that it's
necessary (i.e. there must be at least one use). Can't argue with that.

