paint-brush
Hidden Gems of C and C++ That You Probably Don't Knowby@udalov
265 reads

Hidden Gems of C and C++ That You Probably Don't Know

by IliaAugust 9th, 2024
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

C++ is a great language with too many features. I have experienced multiple "WTF" moments throughout my career as a C++ developer. In this story, you will find the most entertaining aspects of C and C++ programming condensed in the form of tests. No way you can get more than a couple of questions right.
featured image - Hidden Gems of C and C++ That You Probably Don't Know
Ilia HackerNoon profile picture

Are you ready to join majestic world of C and C++ of programming? Do you want to question your existence after few simple lines of C++?


If your answer is "Yeh!", “Yep“ or “Why not?“ - welcome to test your knowledge. You will be given multiple questions related to C or C++.


Please find correct answers and explanations in the end of the story. Good luck!

1. The smallest program

main;


What will happen if you try to compile this program using C compiler?

  1. will not compile
  2. will compile, will not link
  3. will compile and will link

2. The fork

#include <iostream>
#include <unistd.h>

int main() {
    for(auto i = 0; i < 1000; i++)
        std::cout << "Hello world!\n";

    fork();
}


How many lines will this program print?

  1. 1000
  2. less than 1000
  3. more than 1000

3. All you need is indices

#include <iostream>

int main()  {
    int array[] = { 1, 2, 3 };
    std::cout << (4, (1, 2)[array]) << std::endl;
}


What will this program print?

  1. 1
  2. 2
  3. 3
  4. 4
  5. will not compile
  6. undefined

4. Regular expressions

#include <regex>
#include <iostream>

int main() {
    std::regex  re("(.*|.*)*O");
    std::string str("0123456789");

    std::cout << std::regex_match(str, re);

    return 0;
}


How long will it take for this regular expression to match this input string?


  1. 1 ms
  2. 1 sec
  3. 1 min
  4. 1 hour
  5. 1 year
  6. forever

5. Moves and lambdas

#include <iostream>

struct Foo {
    Foo() { std::cout << "Foo()\n"; }
    Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; }
    Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
};

int main() {
    Foo f;
    auto a = [f = std::move(f)]() {
        return std::move(f);
    };

    Foo f2(a());
    return 0;
}


The last line to be printed by this program is…


  1. Foo()
  2. Foo(Foo&&)
  3. Foo(const Foo&)

6. X and bar

#include <iostream>

int x = 0;
int bar(int(x));

int main() {
    std::cout << bar;
}


What will this program print?

  1. 0
  2. 1
  3. 0x0
  4. will not compile
  5. will not link

7. Constructors

#include <iostream>

struct Foo  {
    Foo() { std::cout << "Foo()\n"; }
    Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
    Foo(int) { std::cout << "Foo(int)\n"; }
    Foo(int, int) { std::cout << "Foo(int, int)\n"; }
    Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; }
    Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; }
};

void f(Foo) {}

struct Bar {
    int i, j;

    Bar() {
        f(Foo(i, j));
        f(Foo(i));
        Foo(i, j);
        Foo(i);
        Foo(i, j);
    }
};

int main() { Bar(); }


The last line to be printed by this program is…

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. Foo(int)

Instead of conclusion

I hope you will never find one of this peculiar snippets in the wild.

Answers

  1. The smallest program


    This is legal C code. It will compile and link successfully. It will crash if you try to run it. main; - is global variable.


    In C code you can omit a lot of things. For example you can omit the type of a global variable. By the default compiler will assume this type is an int. Also there is no name mangling in C (unlike in C++), so when linking there is no way to distinguish variable main from function main.


    Thus compiler will compile valid code, and linker will find something named main in object file to link a program.


  2. The fork


    This is more POSIX feature rather than C or C++ feature. Implementations of IO operations use buffers for optimising performance. When you invoke fork, the OS will create copy-on-write duplicate of process memory, the IO-buffers will likely duplicate as well and buffered strings likely will be printed more than 1000 times.


  3. All you need is indices


    Answer is 3


    To understand this code lets take a closer look on how indices in C and C++ work: array[index], is the same as *(array + index), is the same as (index + array) and the same as index[array.

    The second clue is operator,. Its binary operator, it discards left argument and returns right argument.


  4. Regular expressions


    Its impossible to predict what will happen! The behaviour is up to implementation.


    Im my environment this program raises the exception The complexity of an attempted match against a regular expression exceeded a pre-set level.


    Other probable options are surprisingly long time or work as expected. It’s because there are two possible approaches to implement regular expressions.


    First - transform regular expressions to finite automata O(n**2)(n - length of pattern), match string O(m) (m - length of string). This approach doesn’t support backtracking.


    Second - greedy approach + DFS, supports backtracking but prone to exponential time complexity on certain patterns.


  5. Moves and lambdas


    Answer is Foo(const Foo&). Lambdas are immutable by the default, all values captured into lambda with [] are implicitly const. This unlocks idempotent behaviour for lambdas.


    When you move f you create const Foo&&. const Foo&& is a weird type, thus compiler just copies Foo


    There are two ways to fix this:


    1. Create mutable lambda

      auto a = [f = std::move(f)]() mutable {
              return std::move(f);
          };
      


    2. Declare constructor Foo(const Foo&&)


  6. X and bar

    The program will print 1.


    int bar(int(x)); — is weird way to declare function, it is equal to int bar(int x);.

    If you confused with type cast, int bar((int(x)));- this is type cast.


    Than we try to implicitly cast function address tobool, the result of such cast is always true.


    Functionbar() has never been used, thats allow us to dodge unreferenced symbol error while linking.


  7. Constructors

    The last line is Foo(const Foo&, int).


    Foo(i) is variable declaration, the same as Foo i. Thus class member under the name of i is hidden in this scope.