This is C++

(updated )
A tour of modern C++ the way it's supposed to look.

(This document is mostly complete. Feedback is welcome.)

It's 2024, and people still teach C++ as if it was just "C with classes". As if we were stuck in the mid-1990s. No wonder misconceptions still abound, and people keep trying to replace it! Excellent reference material exists. Good tours? Not sure where they can be found, if at all. And it irks me. Takes time and patience to learn this stuff the right way with little or no help. So let me give you a taste of C++ as it should be experienced this century (not even a recent standard):

#include <iostream>

int main() {
    std::cout << "Hello, world!" << std::endl;
}

This is a proper "hello, world" in C++98 or later, and don't let anyone tell you otherwise! Or if you want to get fancy:

#include <iostream>

int main(int argc, char **argv) {
    if (argc > 1)
        std::cout << "Hello, " << argv[1] << "!";
    else
        std::cout << "Hello, world!";
    std::cout << std::endl;
    return 0;
}

I'll assume you know how to install development tools and type g++ hello.cpp -o hello or whatever. Also, this isn't a tutorial. The point is to give you an idea of how to accomplish the simplest practical tasks. Try cppreference.com to learn more. For example, how to read from the keyboard:

#include <iostream>
#include <string>

int main() {
    std::cout << "What's your name? ";

    std::string name;

    if (std::getline(std::cin, name))
        std::cout << "Nice to meet you, " << name << "!";
    else
        std::cout << "OK, bye.";
    std::cout << std::endl;
    return 0;
}

No, it's not like in C. That's the whole point! It's easier than in C, and safer too. And there are things plain C can't easily have, such as type-safe containers:

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> rhyme{"eeny", "meeny", "miny", "moe"};
    for (const auto& i: rhyme)
        std::cout << i << ", ";
    std::cout << "..." << std::endl;
}

Oh, look, you can even have range-for, as it's called. And yes, that's type inference! Thing is, by now we're moving into C++11 territory; if you have an old compiler, say GCC before version 6 or 7, you might have to pass it the -std=c++11 option. Not that you need to worry: anything newer should be able to handle at least C++14 as the default dialect. But we're taking it easy here.

Now, adding the std:: prefix everywhere gets old fast. Luckily we can do better:

#include <iostream>
#include <string>
#include <vector>

using Str = std::string;
using Vec = std::vector<Str>;

int main() {
    Vec sentence;
    Str word;

    std::cout << "Enter a few words (bye to end): ";
    while (std::cin >> word) {
        if (word == "bye")
            break;
        else
            sentence.push_back(word);
    }   
    std::cout << "You entered " << sentence.size() << " words.\n";
}

You can also do it with typedef, the old-fashioned way. I think they're equivalent. Also that's another way to read from an input stream, that works with other data types as well (double d; std::cin >> d; does what it looks like). And you know what else modern C++ has?

#include <iostream>
#include <string>

int main(int argc, char **argv) {
    auto greet = [](const std::string& who) {
        std::cout << "Hello, " << who << "!" << std::endl;
    };
    greet(argc > 1 ? argv[1] : "world");
    return 0;
}

Lambdas, that's what. Complete with inferred return type, even. Not bad for an obsolete clunker of a language, is it? Or how about file I/O:

#include <iostream>
#include <fstream>
#include <string>

int main() {
    const std::string fn = "test.txt";
    {
        std::ofstream sink(fn);
        if (sink.is_open()) {
            sink << "Hello, world!" << std::endl;
        } else {
            std::cerr << "Output file error" << std::endl;
            return 1;
        }
    }
    std::cout << "Reading back data..." << std::endl;
    {
        std::ifstream source(fn);
        std::string input;
        if (source.is_open()) {
            if (std::getline(source, input))
                std::cout << input << std::endl;
            else
                std::cerr << "Read error" << std::endl;
        } else {
            std::cerr << "Output file error" << std::endl;
            return 2;
        }
    }
}

If you ever had to do it in C, the difference is striking: no juggling buffers (to risk overflows); files are closed when they go out of scope (and memory freed). You can focus on your task for a change. This isn't even new anymore.

Other features of interest are much newer, though not exactly new as such. For example, dynamic variables and type-safe unions:

#include <iostream>
#include <string>
#include <any>

int main() {
    std::any data = 42;
    std::cout << std::any_cast<int>(data);
    data = std::string(" is The Answer.");
    std::cout << std::any_cast<std::string>(data) << std::endl;
}

This is C++17 by now; being stuck with an older compiler, I have to enable it explicitly on the command line. But it's possible! It's been a thing for years! You can even have more control if you want:

#include <iostream>
#include <variant>

int main() {
    std::variant<int, char> data = 65;
    std::cout << std::get<int>(data) << " is ";
    data = 'A';
    std::cout << std::get<char>(data) << std::endl;
    return 0;
}

I only know one other language where it's this easy to declare a variant type.

One more thing: so far, this tour focused mostly on things you can also do in C. But C++ goes beyond a C replacement, and not only with advanced features like functional programming and composition of types. Here's something any serious language today is expected to have:

#include <iostream>
#include <string>
#include <regex>

int main() {
    const std::string data = ":123abc45def678gh+ij";
    std::regex re_token("\\d+|[[:alpha:]]+");

    auto start = std::sregex_iterator(
        data.begin(), data.end(), re_token);
    auto finish = std::sregex_iterator();

    for (auto& i = start; i != finish; i++)
        std::cout << i->str() << "\n";
}

To be fair, that's clunky. But hey, regular expressions in the standard library! And with a powerful syntax, too. It's a small thing... until you need them.

In conclusion

The C++98 standard is a quarter century old as of last year. There have been five more major versions since (in fact we've had C++11 for almost half that time). I'm not saying you should use the latest features today. Doubly so as C++ is a huge language. Pick and choose only what you need; it's designed to enable that. But for the love of everything sacred, don't think you're still stuck with printf and plain arrays, either. We can do better. Modern C++ is a mindset. And for the sake of our craft, we must do better.

Addendum

Newer languages brag about memory safety. Between containers, smart pointers and the RAII technique, C++ is plenty safe. Newer languages brag about the restrictions they place on programmers. C++ hands you the tools you need to get work done and reminds you that blades are sharp. It won't hold your hand, or try to save you from yourself. My advice? Try self-discipline instead.

Last but not least: naive C++ code like mine can be slower and use more memory than in other programming languages. (No, it's not due to exceptions; put that myth to rest.) Compilation times aren't great, and error messages can get cryptic when they're about templates. But I'll take it over a language that won't let me use tabs for indentation because its author has Opinions™.

If you want to read the words of a wise, pragmatic language designer, Bjarne Stroustrup has some things to say about his creation.