C++ : Lambdas, local type inference, templates, ...

Martin Sulzmann

Lambdas and local type inference

"Add"

int myAdd(int x, int y) {
  return x+y;
}

void testAdd() {
  cout << "\n" << myAdd(1,3);

  auto myAdd2 = [](int x, int y) -> int { return x+y; };

  cout << "\n" << myAdd2(1,3);

}

C++ lambda notation:

[capture](parameters) -> return_type { function_body }

auto x = 1;

"Plus"

std::function<int(int)> myPlus(int x) {
  return [x](int y) -> int { return x+y; };
}

void testPlus() {
  cout << "\n" << myPlus(1)(3);

  auto inc = myPlus(1);

  cout << "\n" << inc(3);

  auto myPlus2 = [](int x) -> std::function<int(int)> { return [x](int y) -> int { return x+y; }; };

  auto myPlus3 = [](int x)  { return [x](int y)  { return x+y; }; };

  cout << "\n" << myPlus2(1)(3);
  cout << "\n" << myPlus3(1)(3);
}

Partial function application, see inc(3).

Inference of return types, see myPlus3.

Capture by copy versus capture by reference

void testLambdaCapture() {
  auto x = 1;

  auto f_copy = [x] (int y) -> int { return x+y; };
  auto f_ref = [&x] (int y) -> int { return x+y; };

  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
  x = 2;
  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
}

Variable x is used in the lambda function f.

Must specify if we wish capture by copy or by reference.

Capture by copy written [x]. "Easier" for compiler, no need to track non-local variables.

Capture by reference written [&x]. Each access to x will retrieve its current value.

Templates

Simple example

template<typename T>
void mySwap(T& x, T&y) {
  T tmp;

  tmp = x; x = y; y = tmp;
}

void testSwap() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap(x,y);
  cout << "\n" << x << y;
}

Monomorphisation

Templates are "not real".

Templates will be instantiated. That is, we create a copy of the code for each specific instance. This process is called monomorphisation.

The Template-free code will then be processed by the compiler.

void mySwap_int(int& x, int&y) {
  int tmp;

  tmp = x; x = y; y = tmp;
}

void testSwap_int() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap_int(x,y);
  cout << "\n" << x << y;
}

"Show" with Templates

string Show(bool x) {
  return x ? "true" : "false";
}

// Missing type bound, we require Show(T).
template<typename T>
string Show(vector<T> xs) {
  string s;

  s = "[";

  for(int i=0; i < xs.size(); i++) {
    s = s + Show(xs[i]);
    if (i != xs.size() - 1) {
      s = s + ",";
    }
  }
  s = s + "]";

  return s;
}

void testShow() {
  vector<bool> xs;

  xs.push_back(true);
  xs.push_back(false);

  cout << Show(xs);
}

QuickCheck again

We only consider type-specific test data generation.

Recall that arbitrary is a function overloaded on the return type. However, C++ overloading is more restricted. Overloading must be resolved based on the argument types only. Hence, we resort to template specialization.

// Generator interface.
// The actual generator is a parameter-less function.
// We "force" generation by calling this function.
// In essence, we emulate here "lazy" evaluation.
template<typename T>
class Gen {
 public:
  Gen() {};
  Gen(std::function<T()> gen_) { gen = std::function<T()>(gen_); }
  std::function<T()> gen;
  T generate() { return gen(); };
};


// Computing an arbitrary generator.
// Uses template specialization.
template<typename T>
Gen<T> arbitrary();

// Some instances

// bool
template<>
Gen<bool> arbitrary() { return Gen<bool>([] () -> bool { return (rand() % 2 == 0) ? true : false; }); }

// char
template<>
Gen<char> arbitrary() { return Gen<char>([] () -> char { return (char)(rand() % 255); }); }

// vector<char>
template<>
Gen<vector<char>> arbitrary() { return Gen<vector<char>>([] () -> vector<char> {
      auto n = rand() % 11;     // max of 10 characters
      vector<char> vs;
      auto ch = arbitrary<char>();

      for(int i=0; i < n; i++) {
    vs.push_back(ch.generate());
      }
      return vs; });
}


void testGen() {
  auto g = arbitrary<vector<char>>();

  vector<char> vs = g.generate();
  string s(vs.begin(), vs.end());

  cout << "\n" << s;
}

Point to note. It seems impossible to a "generic" arbitrary instance for vector<T>.

Complete source code


// Usage: g++ --std=c++11

#include <functional>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;

//////////////////////////////////////////////////
// Lambdas and local type inference (auto)

// Lambdas in C++
int myAdd(int x, int y) {
  return x+y;
}

void testAdd() {
  cout << "\n" << myAdd(1,3);

  auto myAdd2 = [](int x, int y) -> int { return x+y; };

  cout << "\n" << myAdd2(1,3);

}

// More lambdas.
std::function<int(int)> myPlus(int x) {
  return [x](int y) -> int { return x+y; };
}

void testPlus() {
  cout << "\n" << myPlus(1)(3);

  auto inc = myPlus(1);

  cout << "\n" << inc(3);

  auto myPlus2 = [](int x) -> std::function<int(int)> { return [x](int y) -> int { return x+y; }; };

  auto myPlus3 = [](int x)  { return [x](int y)  { return x+y; }; };

  cout << "\n" << myPlus2(1)(3);
  cout << "\n" << myPlus3(1)(3);
}


// Capture by copy versus capture by reference
void testLambdaCapture() {
  auto x = 1;

  auto f_copy = [x] (int y) -> int { return x+y; };
  auto f_ref = [&x] (int y) -> int { return x+y; };

  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
  x = 2;
  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
}

////////////////////////////////////
// Templates

template<typename T>
void mySwap(T& x, T&y) {
  T tmp;

  tmp = x; x = y; y = tmp;
}

void testSwap() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap<int>(x,y);
  cout << "\n" << x << y;
}

// C++ applies "monomorphisation"
void mySwap_int(int& x, int&y) {
  int tmp;

  tmp = x; x = y; y = tmp;
}


void testSwap_int() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap_int(x,y);
  cout << "\n" << x << y;
}

// Show

string Show(bool x) {
  return x ? "true" : "false";
}

// Missing type bound, we require Show(T).
template<typename T>
string Show(vector<T> xs) {
  string s;

  s = "[";

  for(int i=0; i < xs.size(); i++) {
    s = s + Show(xs[i]);
    if (i != xs.size() - 1) {
      s = s + ",";
    }
  }
  s = s + "]";

  return s;
}

void testShow() {
  vector<bool> xs;

  xs.push_back(true);
  xs.push_back(false);

  cout << Show(xs);
}


// "arbitrary" see QuickCheck


// Generator interface.
// The actual generator is a parameter-less function.
// We "force" generation by calling this function.
// In essence, we emulate here "lazy" evluation.
template<typename T>
class Gen {
 public:
  Gen() {};
  Gen(std::function<T()> gen_) { gen = std::function<T()>(gen_); }
  std::function<T()> gen;
  T generate() { return gen(); };
};


// Computing an arbitrary generator.
// Uses template specialization.
template<typename T>
Gen<T> arbitrary();

// Some instances

// bool
template<>
Gen<bool> arbitrary() { return Gen<bool>([] () -> bool { return (rand() % 2 == 0) ? true : false; }); }

// char
template<>
Gen<char> arbitrary() { return Gen<char>([] () -> char { return (char)(rand() % 255); }); }

// vector<char>
template<>
Gen<vector<char>> arbitrary() { return Gen<vector<char>>([] () -> vector<char> {
      auto n = rand() % 11;    // max of 10 characters
      vector<char> vs;
      auto ch = arbitrary<char>();

      for(int i=0; i < n; i++) {
    vs.push_back(ch.generate());
      }
      return vs; });
}


void testGen() {
  auto g = arbitrary<vector<char>>();

  vector<char> vs = g.generate();
  string s(vs.begin(), vs.end());

  cout << "\n" << s;
}


int main() {
  testAdd();
  testPlus();
  testLambdaCapture();
  testSwap();
  testShow();
  testGen();
}