COMS W4995 C++ for C Programmers

Index of 2023-5/code/0620

Parent directory
Makefile
c2cpp_util.h
values.cpp
variadic.cpp

Makefile

CC  = g++
CXX = g++

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

# turn off warnings about unused variables
CXXFLAGS += -Wno-unused-variable

executables = values variadic

.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

values.cpp

#include <string>
#include <iostream>
#include <iomanip>
#include <utility>
#include "c2cpp_util.h"
using namespace std;

// Wrapper for heap-allocated double
struct D 
{
    D(double x = -1.0) : p{new double(x)} { cout << "(ctor:" << *p << ")"; }
    ~D() { cout << "(dtor:" << *p << ")"; delete p; }

    D(const D& d) : p{new double(*d.p)} { cout << "(copy:" << *p << ")"; }
    D(D&& d) : p{d.p} { d.p = nullptr; cout << "(move:" << *p << ")"; }

    D& operator=(const D& d) = delete;
    D& operator=(D&& d) = delete;

    operator double&() { return *p; }

    double* p;
};

//////////////////////////////////////////////////////////////////////
// 1. lvalue vs. rvalue
//

void main1() 
{
    //////////////////////////////////////////////////////////////////
    // A) Binding lvalue
    //

    D d1 { 1.0 };
    D& rd1 = d1;
    const D& crd1 = d1;
    // D&& rrd1 = d1; // err: cannot bind lvalue to rvalue reference

    //////////////////////////////////////////////////////////////////
    // B) Binding rvalue
    //

    // D& rd2 = D(2.0); // err: cannot bind rvalue ro lvalue reference
    const D& crd3 = D(3.0);
    D&& rrd4 = D(4.0);
    // the temp object is mutable through rvalue ref
    rrd4 += 0.1; 
    // and both temp objects are still alive!
    cout << '[' << *crd3.p << ',' << *rrd4.p << ']';

    //////////////////////////////////////////////////////////////////
    // C) Binding rvalue reference to another rvalue reference
    //

    // D&& rrd4_1 = rrd4; // err: rvalue reference itself is an lvalue!

    D&  rrd4_2 = rrd4; // ok: rvalue reference itself is an lvalue!
    D&& rrd4_3 = std::move(rrd4); // ok: cast rrd4 back to rvalue again

    //////////////////////////////////////////////////////////////////
    // D) Summary of lvalue & rvalue
    //
    // * lvalue
    //     - named variables, function call returning by reference, etc.
    //     - ex) i, s[i]  (assuming i is int, and s is std::string)
    //
    // * rvalue
    //     - literals, function call returning by value, 
    //       cast expression to rvalue reference, etc.
    //     - two sub-categories:
    //         1) prvalues - ex) 2.0, s + s, string("abc")
    //         2) xvalues  - ex) std::move(s)
    //
    // Some surprises:
    //     - glvalue (generalized lvalue) is the union of lvalue and xvalue
    //     - rvalue reference itself is an lvalue because it has a name!
    //     - C string literals (ex. "abc") are lvalues
    //
}

//////////////////////////////////////////////////////////////////////
// 2. Forwarding reference
//
// References:
//     http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html
//     http://thbecker.net/articles/rvalue_references/section_08.html
//     https://en.cppreference.com/w/cpp/utility/forward
//

namespace c2cpp {
    template<typename T> struct remove_reference { typedef T type; };
    template<typename T> struct remove_reference<T&> { typedef T type; };
    template<typename T> struct remove_reference<T&&> { typedef T type; };

    template<typename T>
    T&& forward(typename remove_reference<T>::type& t) noexcept {
        return static_cast<T&&>(t);
    } 
}

template <typename T> void f1(T t)   { t += 0.1; }
template <typename T> void f2(T& t)  { t += 0.2; }
template <typename T> void f3(T&& t) { t += 0.3; }
template <typename T> void f4(T&& t) { f3(std::forward<T>(t)); }
template <typename T> void f5(T&& t) { f3(c2cpp::forward<T>(t)); }

void main2()
{
    // A) f1: we can pass lvalue or rvalue but t is a copy

    cout << "\ntemplate <typename T> void f1(T t) { t += 0.1; }\n";
    D d1 {1.0};  
    f1(d1);  
    f1(D(2.0));  
    cout << '\n';

    // B) f2: cannot pass rvalue

    cout << "\ntemplate <typename T> void f2(T& t) { t += 0.2; }\n";
    f2(d1);  
    // f2(D(2.0)); // err: cannot bind rvalue to T&
    cout << '\n';

    // C) f3: forwarding reference
    //
    // - T&& parameter of function template when T is a type parameter
    //   is called forwarding reference:
    //
    // - becomes rvalue reference when arg is rvalue (which is normal)
    //   ex) f3(D(2.0)): T resolves to D, so T&& becomes D&&
    //
    // - becomes lvalue reference when arg is lvalue (special case)
    //   ex) f3(d1): T resolves to D&, so T&& becomes D& &&, and then D&
    //
    // - reference collapsing in templates and typedefs:
    //
    //         X&  &  collapses to X&
    //         X&  && collapses to X&
    //         X&& &  collapses to X&
    //         X&& && collapses to X&&
    //

    cout << "\ntemplate <typename T> void f3(T&& t) { t += 0.3; }\n";
    f3(d1);     // lvalue arg: T = D&, so T&& is D&
    f3(D(2.0)); // rvalue arg: T = D,  so T&& is D&&
    cout << '\n';

    // D) f4: perfect-forwarding a forwarding reference
    //
    // - std::forward<T> forwards a forwarding reference to another function

    cout << "\ntemplate <typename T> void f4(T&& t) "
        << "{ f3(std::forward<T>(t)); }\n";
    f4(d1);
    f4(D(2.0));
    cout << '\n';

    // E) f5: forward<T> implementation

    cout << "\ntemplate <typename T> void f5(T&& t) "
        << "{ f3(c2cpp::forward<T>(t)); }\n";
    f5(d1);
    f5(D(2.0));
    cout << '\n';
}

int main() 
{
    //main1(); cout << '\n';
    main2(); cout << '\n';
}

variadic.cpp

#include <string>
#include <iostream>
#include <iomanip>
#include <utility>
#include "c2cpp_util.h"
using namespace std;

// Wrapper for heap-allocated double
struct D
{
    D(double x = -1.0) : p{new double(x)} { cout << "(ctor:" << *p << ")"; }
    ~D() { cout << "(dtor:" << *p << ")"; delete p; }

    D(const D& d) : p{new double(*d.p)} { cout << "(copy:" << *p << ")"; }
    D(D&& d) : p{d.p} { d.p = nullptr; cout << "(move:" << *p << ")"; }

    D& operator=(const D& d) = delete;
    D& operator=(D&& d) = delete;

    operator double&() { return *p; }

    double* p;
};

//////////////////////////////////////////////////////////////////////
// 1. Basic variadic template using template recursion
// 
// - Three basic components:
//
//     1) Type parameter pack
//     2) Function parameter pack
//     3) Parameter pack expansion
//

void print() { cout << '\n'; } // base case for template recursion

template <typename T, typename... MoreTs> // type parameter pack
void print(T arg, MoreTs... more_args) // function parameter pack
{
    cout << arg << ' ';
    // parameter pack expansion of a pattern containing parameter pack
    print(more_args...);
}

//////////////////////////////////////////////////////////////////////
// 2. Function parameters as forwarding references to reduce copying
//

/*
void print() { cout << '\n'; }

template <typename T, typename... MoreTs>
void print(T&& arg, MoreTs&&... more_args) // forwarding references
{
    cout << arg << ' ';
    // use std::forward on each arg in the parameter pack
    print(std::forward<MoreTs>(more_args)...);
}
*/

//////////////////////////////////////////////////////////////////////
// 3. Fold expression
//

/*
template <typename... Types>
void print(Types&&... args)
{
    (cout << ... << args) << '\n';

    // ((cout << args << ' '), ...) << '\n';
}
*/

int main()
{
    string s { "Hi" };
    print(s, "ABC", 45, string("xyz"));

    //print(s, "ABC", 45, string("xyz"), D(3.14));
}