Index: doc/papers/OOPSLA17/evaluation/.gitignore
===================================================================
--- doc/papers/OOPSLA17/evaluation/.gitignore	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/.gitignore	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,6 @@
+c-bench
+cpp-bench
+cpp-vbench
+cfa-bench
+*.o
+*.d
Index: doc/papers/OOPSLA17/evaluation/Makefile
===================================================================
--- doc/papers/OOPSLA17/evaluation/Makefile	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/Makefile	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,97 @@
+CC = gcc
+CFA = cfa
+DEPFLAGS = -MMD -MP
+CFLAGS = -O2
+ifdef N
+CFLAGS += -DN=$(N)
+endif
+CXXFLAGS = $(CFLAGS) --std=c++14
+MAKEFILE_NAME = ${firstword ${MAKEFILE_LIST}}
+
+.PHONY : all clean run-c run-cpp run-cfa run
+
+all : c-bench cpp-bench cpp-vbench cfa-bench
+
+# rewrite object generation to auto-determine deps
+COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS)
+COMPILE.cpp = $(CXX) $(DEPFLAGS) $(CXXFLAGS) $(CPPFLAGS)
+COMPILE.cfa = $(CFA) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS)
+
+c-%.o : c-%.c
+	$(COMPILE.c) $(OUTPUT_OPTION) -c $<
+
+cpp-%.o : cpp-%.cpp
+	$(COMPILE.cpp) $(OUTPUT_OPTION) -c $<
+
+cfa-%.o : cfa-%.c
+	$(COMPILE.cfa) $(OUTPUT_OPTION) -c $<
+
+COBJS = c-stack.o c-pair.o c-print.o c-bench.o
+CPPOBJS = cpp-bench.o
+CPPVOBJS = cpp-vstack.o cpp-vbench.o
+CFAOBJS = cfa-stack.o cfa-pair.o cfa-print.o cfa-bench.o
+
+${COBJS} ${CPPOBJS} ${CPPVOBJS} ${CFAOBJS} : ${MAKEFILE_NAME}
+
+CFILES = bench.h $(patsubst c-bench.h,,$(COBJS:.o=.h)) $(COBJS:.o=.c)
+CPPFILES = bench.hpp cpp-stack.hpp cpp-pair.hpp cpp-print.hpp $(CPPOBJS:.o=.cpp)
+CPPVFILES = bench.hpp object.hpp cpp-vprint.hpp $(patsubst cpp-vbench.hpp,,$(CPPVOBJS:.o=.hpp)) $(CPPVOBJS:.o=.cpp)
+CFAFILES = bench.h $(patsubst cfa-bench.h,,$(CFAOBJS:.o=.h)) $(CFAOBJS:.o=.c)
+
+c-bench : $(COBJS) c-bench.o
+	$(COMPILE.c) $(LDFLAGS) $^ -o $@
+
+cpp-bench : $(CPPOBJS) cpp-bench.o
+	$(COMPILE.cpp) $(LDFLAGS) $^ -o $@
+
+cpp-vbench : $(CPPVOBJS) cpp-vbench.o
+	$(COMPILE.cpp) $(LDFLAGS) $^ -o $@
+
+cfa-bench : $(CFAOBJS) cfa-bench.o
+	$(COMPILE.cfa) $(LDFLAGS) $^ -o $@
+
+# include dependency files
+-include $(COBJS:.o=.d)
+-include $(CPPOBJS:.o=.d)
+-include $(CPPVOBJS:.o=.d)
+-include $(CFAOBJS:.o=.d)
+
+clean :
+	rm -f $(COBJS) $(COBJS:.o=.d) c-bench
+	rm -f $(CPPOBJS) $(CPPOBJS:.o=.d) cpp-bench
+	rm -f $(CPPVOBJS) $(CPPVOBJS:.o=.d) cpp-vbench
+	rm -f $(CFAOBJS) $(CFAOBJS:.o=.d) cfa-bench
+
+run-c : c-bench
+	@echo
+	@echo '## C ##'
+	@/usr/bin/time -f 'max_memory:\t%M kilobytes' ./$<
+	@printf 'source_size:\t%8d lines\n' `cat $(CFILES) | wc -l`
+	@printf 'redundant_type_annotations:%8d lines\n' `cat $(CFILES) | fgrep '/***/' -c`
+	@printf 'binary_size:\t%8d bytes\n' `stat -c %s $<`
+
+run-cpp : cpp-bench
+	@echo
+	@echo '## C++ ##'
+	@/usr/bin/time -f 'max_memory:\t %M kilobytes' ./$<
+	@printf 'source_size:\t%8d lines\n' `cat $(CPPFILES) | wc -l`
+	@printf 'redundant_type_annotations:%8d lines\n' `cat $(CPPFILES) | fgrep '/***/' -c`
+	@printf 'binary_size:\t%8d bytes\n' `stat -c %s $<`
+
+run-cppv : cpp-vbench
+	@echo
+	@echo '## C++obj ##'
+	@/usr/bin/time -f 'max_memory:\t%M kilobytes' ./$<
+	@printf 'source_size:\t%8d lines\n' `cat $(CPPVFILES) | wc -l`
+	@printf 'redundant_type_annotations:%8d lines\n' `cat $(CPPVFILES) | fgrep '/***/' -c`
+	@printf 'binary_size:\t%8d bytes\n' `stat -c %s $<`
+
+run-cfa : cfa-bench
+	@echo
+	@echo '## Cforall ##'
+	@/usr/bin/time -f 'max_memory:\t %M kilobytes' ./$<
+	@printf 'source_size:\t%8d lines\n' `cat $(CFAFILES) | wc -l`
+	@printf 'redundant_type_annotations:%8d lines\n' `cat $(CFAFILES) | fgrep '/***/' -c`
+	@printf 'binary_size:\t%8d bytes\n' `stat -c %s $<`
+
+run : run-c run-cfa run-cpp run-cppv
Index: doc/papers/OOPSLA17/evaluation/bench.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/bench.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/bench.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,15 @@
+#pragma once
+#include <stdio.h>
+#include <time.h>
+
+long ms_between(clock_t start, clock_t end) { return (end - start) / (CLOCKS_PER_SEC / 1000); }
+
+#define N 40000000
+#define TIMED(name, code) { \
+	volatile clock_t _start, _end; \
+	_start = clock(); \
+	code \
+	_end = clock(); \
+	printf("%s:\t%8ld ms\n", name, ms_between(_start, _end)); \
+}
+#define REPEAT_TIMED(name, n, code) TIMED( name, for (int _i = 0; _i < n; ++_i) { code } )
Index: doc/papers/OOPSLA17/evaluation/bench.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/bench.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/bench.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,17 @@
+#pragma once
+#include <iomanip>
+#include <iostream>
+#include <time.h>
+
+long ms_between(clock_t start, clock_t end) { return (end - start) / (CLOCKS_PER_SEC / 1000); }
+
+static const int N = 40000000;
+#define TIMED(name, code) { \
+	volatile clock_t _start, _end; \
+	_start = clock(); \
+	code \
+	_end = clock(); \
+	std::cout << name << ":\t" << std::setw(8) << ms_between(_start, _end) \
+		<< std::setw(0) << " ms" << std::endl; \
+}
+#define REPEAT_TIMED(name, n, code) TIMED( name, for (int _i = 0; _i < n; ++_i) { code } )
Index: doc/papers/OOPSLA17/evaluation/c-bench.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-bench.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-bench.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "bench.h"
+#include "c-pair.h"
+#include "c-stack.h"
+#include "c-print.h"
+
+_Bool* new_bool( _Bool b ) {
+	_Bool* q = malloc(sizeof(_Bool)); /***/
+	*q = b;
+	return q;
+}
+
+char* new_char( char c ) {
+	char* q = malloc(sizeof(char)); /***/
+	*q = c;
+	return q;
+}
+
+int* new_int( int i ) {
+	int* q = malloc(sizeof(int)); /***/
+	*q = i;
+	return q;
+}
+
+void* copy_bool( const void* p ) { return new_bool( *(const _Bool*)p ); } /***/
+void* copy_char( const void* p ) { return new_char( *(const char*)p ); } /***/
+void* copy_int( const void* p ) { return new_int( *(const int*)p ); } /***/
+void* copy_pair_bool_char( const void* p ) { return copy_pair( p, copy_bool, copy_char ); } /***/
+void free_pair_bool_char( void* p ) { free_pair( p, free, free ); } /***/
+
+int cmp_bool( const void* a, const void* b ) { /***/
+	return *(const _Bool*)a == *(const _Bool*)b ? 0 : *(const _Bool*)a < *(const _Bool*)b ? -1 : 1; 
+}
+
+int cmp_char( const void* a, const void* b ) { /***/
+	return *(const char*)a == *(const char*)b ? 0 : *(const char*)a < *(const char*)b ? -1 : 1;
+}
+
+int main(int argc, char** argv) {
+	FILE * out = fopen("/dev/null", "w");
+	int maxi = 0, vali = 42;
+	struct stack si = new_stack(), ti;
+
+	REPEAT_TIMED( "push_int", N, push_stack( &si, new_int( vali ) ); )
+	TIMED( "copy_int", 	copy_stack( &ti, &si, copy_int ); /***/ )
+	TIMED( "clear_int", clear_stack( &si, free ); /***/ )
+	REPEAT_TIMED( "pop_int", N, 
+		int* xi = pop_stack( &ti );
+		if ( *xi > maxi ) { maxi = *xi; }
+		free(xi); )
+	REPEAT_TIMED( "print_int", N/2, print( out, "dsds", vali, ":", vali, "\n" ); /***/ )
+
+	struct pair * maxp = new_pair( new_bool(0), new_char('\0') ),
+		* valp = new_pair( new_bool(1), new_char('a') );
+	struct stack sp = new_stack(), tp;
+
+	REPEAT_TIMED( "push_pair", N, push_stack( &sp, copy_pair_bool_char( valp ) ); )
+	TIMED( "copy_pair", copy_stack( &tp, &sp, copy_pair_bool_char ); /***/ )
+	TIMED( "clear_pair", clear_stack( &sp, free_pair_bool_char ); /***/ )
+	REPEAT_TIMED( "pop_pair", N, 
+		struct pair * xp = pop_stack( &tp );
+		if ( cmp_pair( xp, maxp, cmp_bool, cmp_char /***/ ) > 0 ) {
+			free_pair_bool_char( maxp ); /***/
+			maxp = xp;
+		} else {
+			free_pair_bool_char( xp ); /***/
+		} )
+	REPEAT_TIMED( "print_pair", N/2, print( out, "pbcspbcs", *valp, ":", *valp, "\n" ); /***/ )
+	free_pair_bool_char( maxp ); /***/
+	free_pair_bool_char( valp ); /***/
+	fclose(out);
+}
Index: doc/papers/OOPSLA17/evaluation/c-pair.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-pair.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-pair.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,26 @@
+#include <stdlib.h>
+#include "c-pair.h"
+
+struct pair* new_pair(void* first, void* second) {
+	struct pair* p = malloc(sizeof(struct pair)); /***/
+	*p = (struct pair){ first, second }; /***/
+	return p;
+}
+
+struct pair* copy_pair(const struct pair* src, 
+		void* (*copy_first)(const void*), void* (*copy_second)(const void*)) {
+	return new_pair( copy_first(src->first), copy_second(src->second) );
+}
+
+void free_pair(struct pair* p, void (*free_first)(void*), void (*free_second)(void*)) {
+	free_first(p->first);
+	free_second(p->second);
+	free(p);
+}
+
+int cmp_pair(const struct pair* a, const struct pair* b, 
+		int (*cmp_first)(const void*, const void*), int (*cmp_second)(const void*, const void*)) {
+	int c = cmp_first(a->first, b->first);
+	if ( c == 0 ) c = cmp_second(a->second, b->second);
+	return c;
+}
Index: doc/papers/OOPSLA17/evaluation/c-pair.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-pair.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-pair.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,16 @@
+#pragma once
+
+struct pair {
+	void* first;
+	void* second;
+};
+
+struct pair* new_pair(void* first, void* second);
+
+struct pair* copy_pair(const struct pair* src, 
+	void* (*copy_first)(const void*), void* (*copy_second)(const void*));
+
+void free_pair(struct pair* p, void (*free_first)(void*), void (*free_second)(void*));
+
+int cmp_pair(const struct pair* a, const struct pair* b, 
+	int (*cmp_first)(const void*, const void*), int (*cmp_second)(const void*, const void*));
Index: doc/papers/OOPSLA17/evaluation/c-print.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-print.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-print.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,47 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include "c-pair.h"
+#include "c-print.h"
+
+void print_string(FILE* out, const char* x) { fprintf(out, "%s", x); }
+
+void print_bool(FILE* out, _Bool x) { fprintf(out, "%s", x ? "true" : "false"); }
+
+void print_char(FILE* out, char x) {
+	if ( 0x20 <= x && x <= 0x7E ) { fprintf(out, "'%c'", x); }
+	else { fprintf(out, "'\\%x'", x); }
+}
+
+void print_int(FILE* out, int x) { fprintf(out, "%d", x); }
+
+void print_fmt(FILE* out, char fmt, void* p) {
+	switch( fmt ) {
+	case 's': print_string(out, (const char*)p); break; /***/
+	case 'b': print_bool(out, *(_Bool*)p); break; /***/
+	case 'c': print_char(out, *(char*)p); break; /***/
+	case 'd': print_int(out, *(int*)p); break; /***/
+	}
+}
+
+void print(FILE* out, const char* fmt, ...) {
+	va_list args;
+	va_start(args, fmt);
+	for (const char* it = fmt; *it; ++it) {
+		switch( *it ) {
+		case 's': print_string(out, va_arg(args, const char*)); break; /***/
+		case 'b': print_bool(out, va_arg(args, int)); break; /***/
+		case 'c': print_char(out, va_arg(args, int)); break; /***/
+		case 'd': print_int(out, va_arg(args, int)); break; /***/
+		case 'p': {
+			const struct pair x = va_arg(args, const struct pair); /***/
+			fprintf(out, "[");
+			print_fmt(out, *++it, x.first); /***/
+			fprintf(out, ", ");
+			print_fmt(out, *++it, x.second); /***/
+			fprintf(out, "]");
+			break;
+		}
+		}
+	}
+	va_end(args);
+}
Index: doc/papers/OOPSLA17/evaluation/c-print.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-print.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-print.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,9 @@
+#pragma once
+#include <stdio.h>
+
+void print_string(FILE* out, const char* x);
+void print_bool(FILE* out, _Bool x);
+void print_char(FILE* out, char x);
+void print_int(FILE* out, int x);
+
+void print(FILE* out, const char* fmt, ...);
Index: doc/papers/OOPSLA17/evaluation/c-stack.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-stack.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-stack.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,45 @@
+#include <stdlib.h>
+#include "c-stack.h"
+
+struct stack_node {
+	void* value;
+	struct stack_node* next;
+};
+
+struct stack new_stack() { return (struct stack){ NULL }; /***/ }
+
+void copy_stack(struct stack* s, const struct stack* t, void* (*copy)(const void*)) {
+	struct stack_node** crnt = &s->head;
+	for ( struct stack_node* next = t->head; next; next = next->next ) {
+		*crnt = malloc(sizeof(struct stack_node)); /***/
+		**crnt = (struct stack_node){ copy(next->value) }; /***/
+		crnt = &(*crnt)->next;
+	}
+	*crnt = 0;
+}
+
+void clear_stack(struct stack* s, void (*free_el)(void*)) {
+    for ( struct stack_node* next = s->head; next; ) {
+		struct stack_node* crnt = next;
+		next = crnt->next;
+		free_el(crnt->value);
+		free(crnt);
+	}
+	s->head = NULL;
+}
+
+_Bool stack_empty(const struct stack* s) { return s->head == NULL; }
+
+void push_stack(struct stack* s, void* value) {
+	struct stack_node* n = malloc(sizeof(struct stack_node)); /***/
+	*n = (struct stack_node){ value, s->head }; /***/
+	s->head = n;
+}
+
+void* pop_stack(struct stack* s) {
+	struct stack_node* n = s->head;
+	s->head = n->next;
+	void* x = n->value;
+	free(n);
+	return x;
+}
Index: doc/papers/OOPSLA17/evaluation/c-stack.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/c-stack.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/c-stack.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,14 @@
+#pragma once
+
+struct stack_node;
+struct stack {
+	struct stack_node* head;
+};
+
+struct stack new_stack();
+void copy_stack(struct stack* dst, const struct stack* src, void* (*copy)(const void*));
+void clear_stack(struct stack* s, void (*free_el)(void*));
+
+_Bool stack_empty(const struct stack* s);
+void push_stack(struct stack* s, void* value);
+void* pop_stack(struct stack* s);
Index: doc/papers/OOPSLA17/evaluation/cfa-bench.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-bench.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-bench.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,31 @@
+#include <stdio.h>
+#include "bench.h"
+#include "cfa-stack.h"
+#include "cfa-pair.h"
+#include "cfa-print.h"
+
+int main( int argc, char *argv[] ) {
+	FILE * out = fopen( "/dev/null", "w" );
+	int maxi = 0, vali = 42;
+	stack(int) si, ti;
+
+	REPEAT_TIMED( "push_int", N, push( &si, vali ); )
+	TIMED( "copy_int", ti = si; )
+	TIMED( "clear_int", clear( &si ); )
+	REPEAT_TIMED( "pop_int", N, 
+		int xi = pop( &ti ); 
+		if ( xi > maxi ) { maxi = xi; } )
+	REPEAT_TIMED( "print_int", N/2, print( out, vali, ":", vali, "\n" ); )
+
+	pair(_Bool, char) maxp = { (_Bool)0, '\0' }, valp = { (_Bool)1, 'a' };
+	stack(pair(_Bool, char)) sp, tp;
+
+	REPEAT_TIMED( "push_pair", N, push( &sp, valp ); )
+	TIMED( "copy_pair", tp = sp; )
+	TIMED( "clear_pair", clear( &sp ); )
+	REPEAT_TIMED( "pop_pair", N, 
+		pair(_Bool, char) xp = pop( &tp ); 
+		if ( xp > maxp ) { maxp = xp; } )
+	REPEAT_TIMED( "print_pair", N/2, print( out, valp, ":", valp, "\n" ); )
+	fclose(out);
+}
Index: doc/papers/OOPSLA17/evaluation/cfa-pair.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-pair.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-pair.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,35 @@
+#include "cfa-pair.h"
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?<?(R, R); int ?<?(S, S); })
+int ?<?(pair(R, S) p, pair(R, S) q) {
+	return p.first < q.first || ( p.first == q.first && p.second < q.second );
+}
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?<?(R, R); int ?<=?(S, S); })
+int ?<=?(pair(R, S) p, pair(R, S) q) {
+	return p.first < q.first || ( p.first == q.first && p.second <= q.second );
+}
+
+forall(otype R, otype S | { int ?==?(R, R); int ?==?(S, S); })
+int ?==?(pair(R, S) p, pair(R, S) q) {
+	return p.first == q.first && p.second == q.second;
+}
+
+forall(otype R, otype S | { int ?!=?(R, R); int ?!=?(S, S); })
+int ?!=?(pair(R, S) p, pair(R, S) q) {
+	return p.first != q.first || p.second != q.second;
+}
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?>?(R, R); int ?>?(S, S); })
+int ?>?(pair(R, S) p, pair(R, S) q) {
+	return p.first > q.first || ( p.first == q.first && p.second > q.second );
+}
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?>?(R, R); int ?>=?(S, S); })
+int ?>=?(pair(R, S) p, pair(R, S) q) {
+	return p.first > q.first || ( p.first == q.first && p.second >= q.second );
+}
Index: doc/papers/OOPSLA17/evaluation/cfa-pair.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-pair.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-pair.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,28 @@
+#pragma once
+
+forall(otype R, otype S) struct pair {
+	R first;
+	S second;
+};
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?<?(R, R); int ?<?(S, S); })
+int ?<?(pair(R, S) p, pair(R, S) q);
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?<?(R, R); int ?<=?(S, S); })
+int ?<=?(pair(R, S) p, pair(R, S) q);
+
+forall(otype R, otype S | { int ?==?(R, R); int ?==?(S, S); })
+int ?==?(pair(R, S) p, pair(R, S) q);
+
+forall(otype R, otype S | { int ?!=?(R, R); int ?!=?(S, S); })
+int ?!=?(pair(R, S) p, pair(R, S) q);
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?>?(R, R); int ?>?(S, S); })
+int ?>?(pair(R, S) p, pair(R, S) q);
+
+forall(otype R, otype S 
+	| { int ?==?(R, R); int ?>?(R, R); int ?>=?(S, S); })
+int ?>=?(pair(R, S) p, pair(R, S) q);
Index: doc/papers/OOPSLA17/evaluation/cfa-print.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-print.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-print.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include "cfa-pair.h"
+#include "cfa-print.h"
+
+forall(otype T, ttype Params | { void print(FILE*, T); void print(FILE*, Params); })
+void print(FILE* out, T arg, Params rest) {
+	print(out, arg);
+	print(out, rest);
+}
+
+void print(FILE* out, const char* x) { fprintf(out, "%s", x); }
+
+void print(FILE* out, _Bool x) { fprintf(out, "%s", x ? "true" : "false"); }
+
+void print(FILE* out, char x) {
+	if ( 0x20 <= x && x <= 0x7E ) { fprintf(out, "'%c'", x); }
+	else { fprintf(out, "'\\%x'", x); }
+}
+
+void print(FILE* out, int x) { fprintf(out, "%d", x); }
+
+forall(otype R, otype S | { void print(FILE*, R); void print(FILE*, S); })
+void print(FILE* out, pair(R, S) x) {
+	fprintf(out, "[");
+	print(out, x.first);
+	fprintf(out, ", ");
+	print(out, x.second);
+	fprintf(out, "]");
+}
Index: doc/papers/OOPSLA17/evaluation/cfa-print.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-print.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-print.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,14 @@
+#pragma once
+#include <stdio.h>
+#include "cfa-pair.h"
+
+forall(otype T, ttype Params | { void print(FILE*, T); void print(FILE*, Params); })
+void print(FILE* out, T arg, Params rest);
+
+void print(FILE* out, const char* x);
+void print(FILE* out, _Bool x);
+void print(FILE* out, char x);
+void print(FILE* out, int x);
+
+forall(otype R, otype S | { void print(FILE*, R); void print(FILE*, S); })
+void print(FILE* out, pair(R, S) x);
Index: doc/papers/OOPSLA17/evaluation/cfa-stack.c
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-stack.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-stack.c	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,52 @@
+#include <stdlib>
+#include "cfa-stack.h"
+
+forall(otype T) struct stack_node {
+	T value;
+	stack_node(T)* next;
+};
+
+forall(otype T) void ?{}(stack(T)* s) { (&s->head){ 0 }; }
+
+forall(otype T) void ?{}(stack(T)* s, stack(T) t) {
+	stack_node(T)** crnt = &s->head;
+	for ( stack_node(T)* next = t.head; next; next = next->next ) {
+		*crnt = ((stack_node(T)*)malloc()){ next->value }; /***/
+		stack_node(T)* acrnt = *crnt;
+		crnt = &acrnt->next;
+	}
+	*crnt = 0;
+}
+
+forall(otype T) stack(T) ?=?(stack(T)* s, stack(T) t) {
+	if ( s->head == t.head ) return *s;
+	clear(s);
+	s{ t };
+	return *s;
+}
+
+forall(otype T) void ^?{}(stack(T)* s) { clear(s); }
+
+forall(otype T) _Bool empty(const stack(T)* s) { return s->head == 0; }
+
+forall(otype T) void push(stack(T)* s, T value) {
+	s->head = ((stack_node(T)*)malloc()){ value, s->head }; /***/
+}
+
+forall(otype T) T pop(stack(T)* s) {
+	stack_node(T)* n = s->head;
+	s->head = n->next;
+	T x = n->value;
+	^n{};
+	free(n);
+	return x;
+}
+
+forall(otype T) void clear(stack(T)* s) {
+    for ( stack_node(T)* next = s->head; next; ) {
+		stack_node(T)* crnt = next;
+		next = crnt->next;
+		delete(crnt);
+	}
+	s->head = 0;
+}
Index: doc/papers/OOPSLA17/evaluation/cfa-stack.h
===================================================================
--- doc/papers/OOPSLA17/evaluation/cfa-stack.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cfa-stack.h	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,16 @@
+#pragma once
+
+forall(otype T) struct stack_node;
+forall(otype T) struct stack {
+	stack_node(T)* head;
+};
+
+forall(otype T) void ?{}(stack(T)* s);
+forall(otype T) void ?{}(stack(T)* s, stack(T) t);
+forall(otype T) stack(T) ?=?(stack(T)* s, stack(T) t);
+forall(otype T) void ^?{}(stack(T)* s);
+
+forall(otype T) _Bool empty(const stack(T)* s);
+forall(otype T) void push(stack(T)* s, T value);
+forall(otype T) T pop(stack(T)* s);
+forall(otype T) void clear(stack(T)* s);
Index: doc/papers/OOPSLA17/evaluation/cpp-bench.cpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-bench.cpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-bench.cpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,27 @@
+#include <algorithm>
+#include <fstream>
+#include "bench.hpp"
+#include "cpp-stack.hpp"
+#include "cpp-pair.hpp"
+#include "cpp-print.hpp"
+
+int main(int argc, char** argv) {
+	std::ofstream out{"/dev/null"};
+	int maxi = 0, vali = 42;
+	stack<int> si, ti;
+	
+	REPEAT_TIMED( "push_int", N, si.push( vali ); )
+	TIMED( "copy_int", ti = si; )
+	TIMED( "clear_int", si.clear(); )
+	REPEAT_TIMED( "pop_int", N, maxi = std::max( maxi, ti.pop() ); )
+	REPEAT_TIMED( "print_int", N/2, print( out, vali, ":", vali, "\n" ); )
+
+	pair<bool, char> maxp = { false, '\0' }, valp = { true, 'a' };
+	stack<pair<bool, char>> sp, tp;
+	
+	REPEAT_TIMED( "push_pair", N, sp.push( valp ); )
+	TIMED( "copy_pair", tp = sp; )
+	TIMED( "clear_pair", sp.clear(); )
+	REPEAT_TIMED( "pop_pair", N, maxp = std::max( maxp, tp.pop() ); )
+	REPEAT_TIMED( "print_pair", N/2, print( out, valp, ":", valp, "\n" ); )
+}
Index: doc/papers/OOPSLA17/evaluation/cpp-pair.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-pair.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-pair.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,30 @@
+#pragma once
+#include <utility>
+
+template<typename R, typename S> struct pair {
+	R first;
+	S second;
+
+	pair() = default;
+	pair( R&& x, S&& y ) : first( std::move(x) ), second( std::move(y) ) {}
+
+	bool operator< (const pair<R, S>& o) const {
+		return first < o.first || ( first == o.first && second < o.second );
+	}
+
+	bool operator<= (const pair<R, S>& o) const {
+		return first < o.first || ( first == o.first && second <= o.second );
+	}
+
+	bool operator== (const pair<R, S>& o) const { return first == o.first && second == o.second; }
+
+	bool operator!= (const pair<R, S>& o) const { return first != o.first || second != o.second; }
+
+	bool operator> (const pair<R, S>& o) const {
+		return first > o.first || ( first == o.first && second > o.second );
+	}
+
+	bool operator>= (const pair<R, S>& o) const {
+		return first > o.first || ( first == o.first && second >= o.second );
+	}
+};
Index: doc/papers/OOPSLA17/evaluation/cpp-print.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-print.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-print.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,28 @@
+#pragma once
+#include <iomanip>
+#include <iostream>
+#include "cpp-pair.hpp"
+
+template<typename T> void print(std::ostream& out, const T& x) { out << x; }
+
+template<> void print<bool>(std::ostream& out, const bool& x) { out << (x ? "true" : "false"); }
+
+template<> void print<char>(std::ostream& out, const char& x ) {
+	if ( 0x20 <= x && x <= 0x7E ) { out << "'" << x << "'"; }
+	else { out << "'\\" << std::hex << (unsigned int)x << std::setbase(0) << "'"; }
+}
+
+template<typename R, typename S> 
+std::ostream& operator<< (std::ostream& out, const pair<R, S>& x) {
+	out << "[";
+	print(out, x.first);
+	out << ", ";
+	print(out, x.second);
+	return out << "]";
+}
+
+template<typename T, typename... Args> 
+void print(std::ostream& out, const T& arg, const Args&... rest) {
+	out << arg;
+	print(out, rest...);
+}
Index: doc/papers/OOPSLA17/evaluation/cpp-stack.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-stack.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-stack.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,61 @@
+#pragma once
+#include <utility>
+
+template<typename T> class stack {
+	struct node {
+		T value;
+		node* next;
+
+		node( const T& v, node* n = nullptr ) : value(v), next(n) {}
+	};
+	node* head;
+
+	void copy(const stack<T>& o) {
+		node** crnt = &head;
+		for ( node* next = o.head; next; next = next->next ) {
+			*crnt = new node{ next->value }; /***/
+			crnt = &(*crnt)->next;
+		}
+		*crnt = nullptr;
+	}
+public:
+	void clear() {
+	    for ( node* next = head; next; ) {
+			node* crnt = next;
+			next = crnt->next;
+			delete crnt;
+		}
+		head = nullptr;
+	}
+
+	stack() : head(nullptr) {}
+	stack(const stack<T>& o) { copy(o); }
+	stack(stack<T>&& o) : head(o.head) { o.head = nullptr; }
+	~stack() { clear(); }
+
+	stack& operator= (const stack<T>& o) {
+		if ( this == &o ) return *this;
+		clear();
+		copy(o);
+		return *this;
+	}
+
+	stack& operator= (stack<T>&& o) {
+		if ( this == &o ) return *this;
+		head = o.head;
+		o.head = nullptr;
+		return *this;
+	}
+
+	bool empty() const { return head == nullptr; }
+
+	void push(const T& value) { head = new node{ value, head };  /***/ }
+
+	T pop() {
+		node* n = head;
+		head = n->next;
+		T x = std::move(n->value);
+		delete n;
+		return x;
+	}
+};
Index: doc/papers/OOPSLA17/evaluation/cpp-vbench.cpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-vbench.cpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-vbench.cpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,30 @@
+#include <algorithm>
+#include <fstream>
+#include "bench.hpp"
+#include "cpp-vstack.hpp"
+#include "cpp-vprint.hpp"
+#include "object.hpp"
+
+int main(int argc, char** argv) {
+	std::ofstream out{"/dev/null"};
+	integer maxi{ 0 }, vali{ 42 };
+	stack si, ti;
+	
+	REPEAT_TIMED( "push_int", N, si.push( vali ); )
+	TIMED( "copy_int", ti = si; )
+	TIMED( "clear_int", si.clear(); )
+	REPEAT_TIMED( "pop_int", N, maxi = std::max( maxi, ti.pop()->as<integer>() ); /***/ )
+	REPEAT_TIMED( "print_int", N/2, print( out, vali, c_string{":"}, vali, c_string{"\n"} ); )
+
+	ptr<pair> maxp = make<pair>( make<boolean>(false), make<character>('\0') );
+	pair valp{ make<boolean>(true), make<character>('a') };
+	stack sp, tp;
+	
+	REPEAT_TIMED( "push_pair", N, sp.push( valp ); )
+	TIMED( "copy_pair", tp = sp; )
+	TIMED( "clear_pair", sp.clear(); )
+	REPEAT_TIMED( "pop_pair", N, 
+		ptr<pair> xp = as_ptr<pair>( tp.pop() ); /***/
+		if ( *xp > *maxp ) { maxp = std::move(xp); } )
+	REPEAT_TIMED( "print_pair", N/2, print( out, valp, c_string{":"}, valp, c_string{"\n"} ); )
+}
Index: doc/papers/OOPSLA17/evaluation/cpp-vprint.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-vprint.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-vprint.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,10 @@
+#pragma once
+#include <ostream>
+#include "object.hpp"
+
+void print(std::ostream& out, const printable& x) { x.print(out); }
+
+template<typename... Args> void print(std::ostream& out, const printable& x, const Args&... rest) {
+	x.print(out);
+	print(out, rest...);
+}
Index: doc/papers/OOPSLA17/evaluation/cpp-vstack.cpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-vstack.cpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-vstack.cpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,54 @@
+#include "cpp-vstack.hpp"
+#include <utility>
+
+stack::node::node( const object& v, node* n ) : value( v.new_copy() ), next( n ) {}
+
+void stack::copy(const stack& o) {
+	node** crnt = &head;
+	for ( node* next = o.head; next; next = next->next ) {
+		*crnt = new node{ *next->value };
+		crnt = &(*crnt)->next;
+	}
+	*crnt = nullptr;
+}
+
+stack::stack() : head(nullptr) {}
+stack::stack(const stack& o) { copy(o); }
+stack::stack(stack&& o) : head(o.head) { o.head = nullptr; }
+stack::~stack() { clear(); }
+
+stack& stack::operator= (const stack& o) {
+	if ( this == &o ) return *this;
+	clear();
+	copy(o);
+	return *this;
+}
+
+stack& stack::operator= (stack&& o) {
+	if ( this == &o ) return *this;
+	head = o.head;
+	o.head = nullptr;
+	return *this;
+}
+
+void stack::clear() {
+    for ( node* next = head; next; ) {
+		node* crnt = next;
+		next = crnt->next;
+		delete crnt;
+	}
+	head = nullptr;
+}
+
+
+bool stack::empty() const { return head == nullptr; }
+
+void stack::push(const object& value) { head = new node{ value, head }; /***/ }
+
+ptr<object> stack::pop() {
+	node* n = head;
+	head = n->next;
+	ptr<object> x = std::move(n->value);
+	delete n;
+	return x;
+}
Index: doc/papers/OOPSLA17/evaluation/cpp-vstack.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/cpp-vstack.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/cpp-vstack.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,26 @@
+#pragma once
+#include "object.hpp"
+
+class stack {
+	struct node {
+		ptr<object> value;
+		node* next;
+
+		node( const object& v, node* n = nullptr );
+	};
+	node* head;
+
+	void copy(const stack& o);
+public:
+	stack();
+	stack(const stack& o);
+	stack(stack&& o);
+	~stack();
+	stack& operator= (const stack& o);
+	stack& operator= (stack&& o);
+
+	void clear();
+	bool empty() const;
+	void push(const object& value);
+	ptr<object> pop();
+};
Index: doc/papers/OOPSLA17/evaluation/object.hpp
===================================================================
--- doc/papers/OOPSLA17/evaluation/object.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/object.hpp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,199 @@
+#pragma once
+#include <cstddef>
+#include <exception>
+#include <iomanip>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <typeinfo>
+#include <typeindex>
+
+class bad_cast : public std::exception {
+	std::string why;
+public:
+	bad_cast( const std::type_index& f, const std::type_index& t ) : std::exception() {
+		why = std::string{"bad cast of "} + f.name() + " to " + t.name();
+	}
+	~bad_cast() override = default;
+	
+	const char* what() const noexcept override { return why.c_str(); }
+};
+
+template<typename T> std::type_index class_of() { return { typeid(T) }; }
+
+template<typename T> using ptr = std::unique_ptr<T>;
+
+struct object {
+	std::type_index get_class() const { return { this ? typeid(*this) : typeid(std::nullptr_t) }; }
+
+	template<typename T> T& as() {
+		T* p = dynamic_cast<T*>(this);
+		if ( !p ) throw bad_cast{ get_class(), class_of<T>() };
+		return *p;
+	}
+
+	template<typename T> const T& as() const {
+		const T* p = dynamic_cast<const T*>(this);
+		if ( !p ) throw bad_cast{ get_class(), class_of<T>() };
+		return *p;
+	}
+
+	virtual ptr<object> new_inst() const = 0;
+	virtual ptr<object> new_copy() const = 0;
+	virtual object& operator= (const object&) = 0;
+	virtual ~object() = default;
+};
+
+template<typename T, typename... Args> static inline ptr<T> make(Args&&... args) {
+	return std::make_unique<T>(std::forward<Args>(args)...);
+}
+
+template<typename To, typename From> 
+ptr<To> as_ptr( ptr<From>&& p ) { return ptr<To>{ &p.release()->template as<To>() }; }
+
+struct ordered : public virtual object {
+	virtual int cmp(const ordered&) const = 0;
+
+	bool operator< (const ordered& that) const { return cmp(that) < 0; }
+	bool operator<= ( const ordered& that ) const { return cmp(that) <= 0; }
+	bool operator== ( const ordered& that ) const { return cmp(that) == 0; }
+	bool operator!= ( const ordered& that ) const { return cmp(that) != 0; }
+	bool operator> ( const ordered& that ) const { return cmp(that) > 0; }
+	bool operator>= ( const ordered& that ) const { return cmp(that) >= 0; }
+};
+
+struct printable : public virtual object {
+	virtual void print(std::ostream&) const = 0;
+};
+
+class boolean : public ordered, public printable {
+	bool x;
+public:
+	boolean() = default;
+	boolean(bool x) : x(x) {}
+	boolean(const boolean&) = default;
+	boolean(boolean&&) = default;
+	ptr<object> new_inst() const override { return make<boolean>(); }
+	ptr<object> new_copy() const override { return make<boolean>(*this); }
+	boolean& operator= (const boolean& that) {
+		x = that.x;
+		return *this;	
+	}
+	object& operator= (const object& that) override { return *this = that.as<boolean>(); } /***/
+	boolean& operator= (boolean&&) = default;
+	~boolean() override = default;
+
+	int cmp(const boolean& that) const { return x == that.x ? 0 : x == false ? -1 : 1; }
+	int cmp(const ordered& that) const override { return cmp( that.as<boolean>() ); } /***/
+
+	void print(std::ostream& out) const override { out << (x ? "true" : "false"); }
+};
+
+class character : public ordered, public printable {
+	char x;
+public:
+	character() = default;
+	character(char x) : x(x) {}
+	character(const character&) = default;
+	character(character&&) = default;
+	ptr<object> new_inst() const override { return make<character>(); }
+	ptr<object> new_copy() const override { return make<character>(*this); }
+	character& operator= (const character& that) {
+		x = that.x;
+		return *this;	
+	}
+	object& operator= (const object& that) override { return *this = that.as<character>(); } /***/
+	character& operator= (character&&) = default;
+	~character() override = default;
+
+	int cmp(const character& that) const { return x == that.x ? 0 : x < that.x ? -1 : 1; }
+	int cmp(const ordered& that) const override { return cmp( that.as<character>() ); } /***/
+
+	void print(std::ostream& out) const override {
+		if ( 0x20 <= x && x <= 0x7E ) { out << "'" << x << "'"; }
+		else { out << "'\\" << std::hex << (unsigned int)x << std::setbase(0) << "'"; }
+	}
+};
+
+class integer : public ordered, public printable {
+	int x;
+public:
+	integer() = default;
+	integer(int x) : x(x) {}
+	integer(const integer&) = default;
+	integer(integer&&) = default;
+	ptr<object> new_inst() const override { return make<integer>(); }
+	ptr<object> new_copy() const override { return make<integer>(*this); }
+	integer& operator= (const integer& that) {
+		x = that.x;
+		return *this;	
+	}
+	object& operator= (const object& that) override { return *this = that.as<integer>(); } /***/
+	integer& operator= (integer&&) = default;
+	~integer() override = default;
+
+	int cmp(const integer& that) const { return x == that.x ? 0 : x < that.x ? -1 : 1; }
+	int cmp(const ordered& that) const override { return cmp( that.as<integer>() ); } /***/
+
+	void print(std::ostream& out) const override { out << x; }
+};
+
+class c_string : public printable {
+	static constexpr const char* empty = "";
+	const char* s;
+public:
+	c_string() : s(empty) {}
+	c_string(const char* s) : s(s) {}
+	c_string(const c_string&) = default;
+	c_string(c_string&&) = default;
+	ptr<object> new_inst() const override { return make<c_string>(); }
+	ptr<object> new_copy() const override { return make<c_string>(s); }
+	c_string& operator= (const c_string& that) {
+		s = that.s;
+		return *this;
+	}
+	object& operator= (const object& that) override { return *this = that.as<c_string>(); } /***/
+	c_string& operator= (c_string&&) = default;
+	~c_string() override = default;
+
+	void print(std::ostream& out) const override { out << s; }
+};
+
+class pair : public ordered, public printable {
+	ptr<object> x;
+	ptr<object> y;
+public:
+	pair() = default;
+	pair(ptr<object>&& x, ptr<object>&& y) : x(std::move(x)), y(std::move(y)) {}
+	pair(const pair& that) : x(that.x->new_copy()), y(that.y->new_copy()) {}
+	pair(pair&& that) : x(std::move(that.x)), y(std::move(that.y)) {}
+	ptr<object> new_inst() const override { return make<pair>(); }
+	ptr<object> new_copy() const override { return make<pair>(x->new_copy(), y->new_copy()); }
+	pair& operator= (const pair& that) {
+		x = that.x->new_copy();
+		y = that.y->new_copy();
+		return *this;
+	}
+	object& operator= (const object& that) override { return *this = that.as<pair>(); } /***/
+	pair& operator= (pair&& that) {
+		x = std::move(that.x);
+		y = std::move(that.y);
+		return *this;
+	}
+	~pair() override = default;
+
+	int cmp(const pair& that) const {
+		int c = x->as<ordered>().cmp( that.x->as<ordered>() ); /***/
+		if ( c != 0 ) return c;
+		return y->as<ordered>().cmp( that.y->as<ordered>() ); /***/
+	}
+	int cmp(const ordered& that) const override { return cmp( that.as<pair>() ); }
+
+	void print(std::ostream& out) const override {
+		out << "[";
+		x->as<printable>().print(out); /***/
+		out << ", ";
+		y->as<printable>().print(out); /***/
+		out << "]";
+	}
+};
Index: doc/papers/OOPSLA17/evaluation/timing.dat
===================================================================
--- doc/papers/OOPSLA17/evaluation/timing.dat	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/timing.dat	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,11 @@
+"400 million repetitions"	"C"	"\\CFA{}"	"\\CC{}"	"\\CC{obj}"
+"push\nint"	3002	2459	1520	3305
+"copy\nint"	2985	2057	1521	3152
+"clear\nint"	1374	827	718	1469
+"pop\nint"	1416	1221	717	5467
+"print\nint"	5656	6758	3120	3121
+"push\npair"	4214	2752	946	6826
+"copy\npair"	6127	2105	993	7330
+"clear\npair"	2881	885	711	3564
+"pop\npair"	3046	5434	783	26538
+"print\npair"	7514	10714	8717	16525
Index: doc/papers/OOPSLA17/evaluation/timing.gp
===================================================================
--- doc/papers/OOPSLA17/evaluation/timing.gp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
+++ doc/papers/OOPSLA17/evaluation/timing.gp	(revision f4e3419d14c62967d6b0785e8d91d429cc04b95b)
@@ -0,0 +1,26 @@
+# set terminal pdfcairo linewidth 3 size 6,3
+# set output "timing.pdf"
+set terminal pslatex size 6.25,2.125 color solid
+set output "timing.tex"
+
+set pointsize 2.0
+set grid linetype 0
+set style data histogram
+set style histogram cluster gap 2
+set style fill solid border -1
+set offset -0.5,-0.35
+set boxwidth 0.8
+
+set key top left reverse Left
+
+set style fill solid noborder
+set linetype 1 lc rgb 'black'
+set linetype 2 lc rgb 'red'
+set linetype 3 lc rgb 'blue'
+set linetype 4 lc rgb 'green'
+
+SCALE=1000
+set ylabel "seconds"
+
+# set datafile separator ","
+plot for [COL=2:5] 'evaluation/timing.dat' using (column(COL)/SCALE):xticlabels(1) title columnheader
