COMS W4995 C++ for C Programmers

Index of 2023-5/code/0615

Parent directory
Makefile
c2cpp_util.h
functor.cpp
rand1.cpp
rand2.cpp

Makefile

CC  = g++
CXX = g++

CFLAGS   = -Wall
CXXFLAGS = -Wall -std=c++17

executables = functor rand1 rand2

.PHONY: default
default: $(executables)

.PHONY: clean
clean:
	rm -f a.out core *.o $(executables)

.PHONY: all
all: clean default

c2cpp_util.h

#include <cxxabi.h>
#include <typeinfo>
#include <cassert>
#include <cstdlib>

#ifdef __GNUC__
// use abi::__cxa_demangle to demangle type name in g++ & clang++
#define PRINT_TYPE(os, x) do {                                      \
    int status;                                                     \
    char *s = abi::__cxa_demangle(typeid(x).name(), 0, 0, &status); \
    assert(status == 0);                                            \
    os << s << '\n';                                                \
    std::free(s);                                                   \
} while (0)
#else
// MSVC returns human-readable type name, according to cppreference.com
#define PRINT_TYPE(os, x) do {                                      \
    os << typeid(x).name() << '\n';                                 \
} while (0)
#endif

functor.cpp

#include <array>
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <functional>
using namespace std;

// Pretty much the same as std::for_each
template <typename I, typename F>
void For_Each(I b, I e, F f)
{
    while (b != e) {
        f(*b);
        ++b;
    }
}

void print_int(int x)  { cout << x << " "; }

struct Printer_of_int {
    void operator()(int x) const { cout << x << ' '; }
};

// From: https://en.cppreference.com/w/cpp/language/member_template
struct Printer {
    std::ostream& os;
    Printer(std::ostream& os) : os(os) {}
    // template member function
    template<typename T>
        void operator()(const T& obj) { os << obj << ' '; }
};

int main()
{
    vector<int> v1 { 100, 101, 102, 103, 104 };

    vector<int> v2;

    copy(v1.begin(), v1.end(), back_inserter(v2));
    
    For_Each(v2.begin(), v2.end(), &print_int);  cout << '\n';
}

/* Lecture outline:

1. Function objects (aka functors)

    - operator()
    - template member function

code:

    For_Each(v2.begin(), v2.end(), Printer_of_int{});  cout << '\n';
    For_Each(v2.begin(), v2.end(), Printer{cout});  cout << '\n';


2. STL function objects

    - Stateless unary & binary functions
    - Binding argumnets with bind()
        - std::placeholders::_1 represents the future argument
          that will be passed to the functor generated by bind()
    - Composing functions with bind()

code:

    For_Each(v2.begin(), v2.end(), negate<int>());

    transform(v1.begin(), v1.end(), back_inserter(v2),
            negate<int>());
    transform(v1.begin(), v1.end(), back_inserter(v2),
            bind(plus<int>(), placeholders::_1, 300));
    transform(v1.begin(), v1.end(), back_inserter(v2),
            bind(plus<int>(),
                bind(negate<int>(), placeholders::_1),
                300));


3. Lambda

    - Lambda expression
    - Capturing local variables
    - Type of lambda

code:

    transform(v1.begin(), v1.end(), back_inserter(v2),
            [] (int x) {
                return (-x + 300) * 10;
            });

    double a = 2.0, b = 100;

    auto lambda = [&a, b] (int x) {
        return a * x + b;
    };

    a = 4.0, b = 1'000'000;

    transform(v1.begin(), v1.end(), back_inserter(v2), lambda);
*/

rand1.cpp

#include <cstdlib>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <functional>
using namespace std;

int main()
{
    // Random number generator using C functions
    srandom(time(nullptr)); // current time as seed
    auto engine = &random; // random() in the C library as engine
    auto distro = [] (auto some_engine) {
        // get an unsigned random integer from engine,
        // and "distribute" it in [-10,10]
        return some_engine() % 21 - 10;
    };

    /*
    // Random number generator in C++
    random_device rd;
    random_device::result_type seed;
    seed = (rd.entropy() > 0) ? rd() : time(nullptr);
    default_random_engine engine { seed };
    uniform_int_distribution<> distro { -10, 10 };
    */

    map<int,int> m;
    for (int i = 0; i < 1'000'000; ++i) {
        int x = distro(engine);
        ++m[x];
    }
    for (const auto& e : m) {
        cout << setw(4) << e.first << '|' << e.second << '\n';
    }
}

rand2.cpp

#include <cstdlib>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <functional>
#include "c2cpp_util.h"
using namespace std;

int main()
{
    random_device rd;
    random_device::result_type seed;
    seed = (rd.entropy() > 0) ? rd() : time(nullptr);

    //default_random_engine engine { seed };
    //uniform_int_distribution<> distro { -10, 10 };

    mt19937 engine { seed };
    normal_distribution<> distro { 0.0, 3.5 };

    map<int,int> m;
    int maximum = 0;

    for (int i = 0; i < 1'000'000; ++i) {

        int x = lround( distro(engine) );

        int occurence = ++m[x];
        maximum = max(maximum, occurence);
    }

    int bar_scale = maximum / 50;
    for (const auto& e : m) {
        cout << setw(4) << e.first << '|' 
            << setw(7) << e.second << '|'
            << string(e.second / bar_scale, '*') << '\n';
    }
}

/* Lecture outline:

1. Generating normal-distributed random numbers

    - normal_distribution<> distro { 0.0, 3.5 };

2. Mersenne Twister engine

    - mt19937 engine { seed };

3. Danger of narrowing conversion

    - subtle bug without lround()

        int x { distro(engine) }; // compiler warning
        int x = distro(engine); // no warning, but subtle bug!

4. Stateful functor

    - binding engine to distro:

        //int x = lround( distro(engine) );
        auto generator = bind(distro, engine);
        int x = lround( generator() );

        - why the same number million times?

    - capturing distro and engine in a lambda

        auto generator = 
            bind(distro, engine);
            //bind(distro, ref(engine));
            //[distro, engine] () { return distro(engine); };
            //[distro, engine] () mutable { return distro(engine); };
            //[=] () mutable { return distro(engine); };
            //[&] () { return distro(engine); };

        int x = lround( generator() );

    - better yet, construct generator outside the loop

        // The generator object should be constructed outside the loop.
        auto generator = bind(distro, engine);

        for (int i = 0; i < 1'000'000; ++i) {

5. *Very* stateful functor

    - why was it so slow when engine was being copied in the loop?

        cout << '\n' << typeid(engine).name() << "\n\n";
        PRINT_TYPE(cout, engine);
        cout << "\nsizeof(engine): " << sizeof(engine) << "\n\n";
*/