Parent directory Makefile c2cpp_util.h values.cpp variadic.cpp
Download
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
#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
#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'; }
#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)); }