Parent directory
Makefile
values.cpp
variadic.cpp
CC = g++
CXX = g++
CFLAGS = -g -Wall
CXXFLAGS = -g -Wall -std=c++17
# turn off warnings about unused variables
CXXFLAGS += -Wno-unused-variable
executables = values variadic
.PHONY: default
default: $(executables)
values: values.o
variadic: variadic.o
.PHONY: clean
clean:
rm -f *~ a.out core *.o $(executables)
.PHONY: all
all: clean default
#include <string>
#include <iostream>
#include <iomanip>
#include <utility>
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; p = nullptr; }
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; }
operator const double&() const { 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 to 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 << "," << rrd4 << "] ";
//////////////////////////////////////////////////////////////////
// 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
//
cout << "(bye) ";
}
//////////////////////////////////////////////////////////////////////
// 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 << "(d1:" << d1 << ") \n";
/*
* So f2(T&) can accept lvalues but not rvalues.
*
* The next example, f3(T&&), we may assume will accept rvalues,
* but not lvalues.
*
* How can we, then, write a template function that can accept
* both lvalues and rvalues by reference?
*
* C++'s answer is to tweak the meaning of T&& in template settings.
*/
// 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 << "(d1:" << d1 << ") \n";
/*
* Suppose you'd like to write f4(T&& t) that does some work and then
* pass t on to f3(T&&) preserving t's lvalue/rvalue-ness. That is,
* we'd like to forward a forwarding reference.
*
* If we simply write f3(t), then f3 will always receive an lvalue --
* recall that any variable name (even if it's an rvalue reference) is
* an lvalue. (You can confirm this by calling f2(t) from f4(T&& t).)
*
* C++'s answer is std::forward<T>.
*/
// 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 << "(d1:" << d1 << ") \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 << "(d1:" << d1 << ") \n";
}
int main() {
main1(); cout << '\n';
main2(); cout << '\n';
}
#include <string>
#include <iostream>
#include <iomanip>
#include <utility>
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; p = nullptr; }
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; }
operator const double&() const { 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';
// The expression above was a "binary left fold" expression.
// See https://en.cppreference.com/w/cpp/language/fold for reference.
//
// We can use a unary right fold with a comman operator to add a space.
// ((cout << args << ' '), ...) << '\n';
}
*/
int main() {
string s { "Hi" };
print(s, "ABC", 45, string("xyz"));
//print(s, "ABC", 45, string("xyz"), D(3.14));
}