Index: doc/generic_types/evaluation/Makefile
===================================================================
--- doc/generic_types/evaluation/Makefile	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/Makefile	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -1,5 +1,8 @@
 CFA = my-cfa
 DEPFLAGS = -MMD -MP
-CFLAGS = -O2 -flto
+CFLAGS = -O2
+ifdef N
+CFLAGS += -DN=$(N)
+endif
 CXXFLAGS = $(CFLAGS) --std=c++14
 
@@ -25,8 +28,8 @@
 	$(COMPILE.cfa) $(OUTPUT_OPTION) -c $<
 
-COBJS = c-stack.o c-pair.o
+COBJS = c-stack.o c-pair.o c-print.o
 CPPOBJS = 
 CPPVOBJS = cpp-vstack.o
-CFAOBJS = cfa-stack.o cfa-pair.o
+CFAOBJS = cfa-stack.o cfa-pair.o cfa-print.o
 
 c-bench: c-bench.c c-bench.d $(COBJS)
@@ -65,5 +68,5 @@
 	@echo '## Cforall ##'
 	@/usr/bin/time -f 'max_memory:\t %M kilobytes' ./cfa-bench
-	@printf 'source_size:\t%8d lines\n' `cat cfa-bench.c bench.h cfa-stack.h cfa-stack.c | wc -l`
+	@printf 'source_size:\t%8d lines\n' `cat cfa-bench.c bench.h cfa-stack.h cfa-stack.c cfa-print.h cfa-print.c | wc -l`
 	@printf 'binary_size:\t%8d bytes\n' `stat -c %s cfa-bench`
 
@@ -72,5 +75,5 @@
 	@echo '## C++ ##'
 	@/usr/bin/time -f 'max_memory:\t %M kilobytes' ./cpp-bench
-	@printf 'source_size:\t%8d lines\n' `cat cpp-bench.cpp bench.hpp cpp-stack.hpp | wc -l`
+	@printf 'source_size:\t%8d lines\n' `cat cpp-bench.cpp bench.hpp cpp-stack.hpp cpp-print.hpp | wc -l`
 	@printf 'binary_size:\t%8d bytes\n' `stat -c %s cpp-bench`
 
Index: doc/generic_types/evaluation/bench.h
===================================================================
--- doc/generic_types/evaluation/bench.h	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/bench.h	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -4,5 +4,7 @@
 #include <time.h>
 
-#define N 50000000
+#ifndef N
+#define N 40000000
+#endif
 
 long ms_between(clock_t start, clock_t end) {
Index: doc/generic_types/evaluation/bench.hpp
===================================================================
--- doc/generic_types/evaluation/bench.hpp	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/bench.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -5,5 +5,7 @@
 #include <time.h>
 
-static const int N = 50000000;
+#ifndef N
+static const int N = 40000000;
+#endif
 
 long ms_between(clock_t start, clock_t end) {
Index: doc/generic_types/evaluation/c-bench.c
===================================================================
--- doc/generic_types/evaluation/c-bench.c	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/c-bench.c	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -1,6 +1,8 @@
+#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 ) {
@@ -89,3 +91,17 @@
 	)
 	free_pair_bool_char( max2 );
+
+	FILE* out = fopen("c-out.txt", "w");
+	REPEAT_TIMED( "print_int",
+		print( out, "dsds", rand(), ":", rand(), "\n" );
+	)
+
+	REPEAT_TIMED( "print_pair", 
+		struct pair p1 = ((struct pair){ new_bool(rand() & 0x1), new_char(rand() & 0x7F) });
+		struct pair p2 = ((struct pair){ new_bool(rand() & 0x1), new_char(rand() & 0x7F) });
+		print( out, "pbcspbcs", p1, ":", p2, "\n" );
+		free(p1.first); free(p1.second);
+		free(p2.first); free(p2.second);
+	)
+	fclose(out);
 }
Index: doc/generic_types/evaluation/c-print.c
===================================================================
--- doc/generic_types/evaluation/c-print.c	(revision 0d1009093cfc499402ee89b89210b397039f5412)
+++ doc/generic_types/evaluation/c-print.c	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -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/generic_types/evaluation/c-print.h
===================================================================
--- doc/generic_types/evaluation/c-print.h	(revision 0d1009093cfc499402ee89b89210b397039f5412)
+++ doc/generic_types/evaluation/c-print.h	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -0,0 +1,13 @@
+#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/generic_types/evaluation/cfa-bench.c
===================================================================
--- doc/generic_types/evaluation/cfa-bench.c	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/cfa-bench.c	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -1,7 +1,9 @@
 #include <stdlib>
 #include <stdlib.h>
+#include <stdio.h>
 #include "pair"
 #include "bench.h"
 #include "cfa-stack.h"
+#include "cfa-print.h"
 
 int main(int argc, char** argv) {
@@ -45,3 +47,14 @@
 		max2 = max( max2, pop( &t2 ) );
 	)
+
+	FILE* out = fopen("cfa-out.txt", "w");
+	REPEAT_TIMED( "print_int",
+		print( out, rand(), ":", rand(), "\n" );
+	)
+
+	REPEAT_TIMED( "print_pair",
+		print( out, (pair(_Bool, char)){ rand() & 0x1, rand() & 0x7F }, ":",
+				(pair(_Bool, char)){ rand() & 0x1, rand() & 0x7F }, "\n" );
+	)
+	fclose(out);
 }
Index: doc/generic_types/evaluation/cfa-print.c
===================================================================
--- doc/generic_types/evaluation/cfa-print.c	(revision 0d1009093cfc499402ee89b89210b397039f5412)
+++ doc/generic_types/evaluation/cfa-print.c	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include "pair"
+#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/generic_types/evaluation/cfa-print.h
===================================================================
--- doc/generic_types/evaluation/cfa-print.h	(revision 0d1009093cfc499402ee89b89210b397039f5412)
+++ doc/generic_types/evaluation/cfa-print.h	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <stdio.h>
+#include "pair"
+
+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/generic_types/evaluation/cpp-bench.cpp
===================================================================
--- doc/generic_types/evaluation/cpp-bench.cpp	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/cpp-bench.cpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -1,7 +1,9 @@
 #include <algorithm>
+#include <fstream>
 #include <stdlib.h>
 #include <utility>
 #include "bench.hpp"
 #include "cpp-stack.hpp"
+#include "cpp-print.hpp"
 
 int main(int argc, char** argv) {
@@ -45,3 +47,13 @@
 		max2 = std::max( max2, t2.pop() );
 	)
+
+	std::ofstream out{"cpp-out.txt"};
+	REPEAT_TIMED( "print_int",
+		print( out, rand(), ":", rand(), "\n" );
+	)
+
+	REPEAT_TIMED( "print_pair",
+		print( out, std::pair<bool, char>{ rand() & 0x1, rand() & 0x7F }, ":",
+				std::pair<bool, char>{ rand() & 0x1, rand() & 0x7F }, "\n" );
+	)
 }
Index: doc/generic_types/evaluation/cpp-print.hpp
===================================================================
--- doc/generic_types/evaluation/cpp-print.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
+++ doc/generic_types/evaluation/cpp-print.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <iomanip>
+#include <iostream>
+#include <utility>
+
+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 std::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/generic_types/evaluation/cpp-stack.hpp
===================================================================
--- doc/generic_types/evaluation/cpp-stack.hpp	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/cpp-stack.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -33,4 +33,5 @@
 			delete crnt;
 		}
+		head = nullptr;
 	}
 
Index: doc/generic_types/evaluation/cpp-vbench.cpp
===================================================================
--- doc/generic_types/evaluation/cpp-vbench.cpp	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/cpp-vbench.cpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -1,6 +1,8 @@
 #include <algorithm>
+#include <fstream>
 #include <stdlib.h>
 #include "bench.hpp"
 #include "cpp-vstack.hpp"
+#include "cpp-vprint.hpp"
 #include "object.hpp"
 
@@ -45,6 +47,18 @@
 		std::make_unique<character>('\0') );
 	REPEAT_TIMED( "pop_bool_char",
-		std::unique_ptr<object> x = t2.pop();
-		if ( x->as<pair>() > *max2 ) { max2.reset( static_cast<pair*>(x.release()) ); }
+		std::unique_ptr<pair> x = as_ptr<pair>( t2.pop() );
+		if ( *x > *max2 ) { max2 = std::move(x); }
+	)
+
+	std::ofstream out{"cpp-vout.txt"};
+	REPEAT_TIMED( "print_int",
+		print( out, integer{rand()}, c_string{":"}, integer{rand()}, c_string{"\n"} );
+	)
+
+	REPEAT_TIMED( "print_pair", 
+		print( out, pair{ std::make_unique<boolean>( rand() & 0x1 ), 
+			std::make_unique<character>( rand() & 0x7F ) }, c_string{":"}, 
+			pair{ std::make_unique<boolean>( rand() & 0x1 ), 
+			std::make_unique<character>( rand() & 0x7F ) }, c_string{"\n"} );
 	)
 }
Index: doc/generic_types/evaluation/cpp-vprint.hpp
===================================================================
--- doc/generic_types/evaluation/cpp-vprint.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
+++ doc/generic_types/evaluation/cpp-vprint.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -0,0 +1,12 @@
+#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/generic_types/evaluation/object.hpp
===================================================================
--- doc/generic_types/evaluation/object.hpp	(revision ff178ee63b849cf9132ad27f35668a68d5abc180)
+++ doc/generic_types/evaluation/object.hpp	(revision 0d1009093cfc499402ee89b89210b397039f5412)
@@ -1,6 +1,9 @@
 #pragma once
 
+#include <cstddef>
 #include <exception>
+#include <iomanip>
 #include <memory>
+#include <ostream>
 #include <string>
 #include <typeinfo>
@@ -24,18 +27,18 @@
 class object {
 public:
-	std::type_index get_class() const { return { typeid(*this) }; }
+	std::type_index get_class() const { return { this ? typeid(*this) : typeid(std::nullptr_t) }; }
 
 	template<typename T>
 	T& as() {
-		std::type_index from = get_class(), to = class_of<T>();
-		if ( from != to ) throw bad_cast{ from, to };
-		return reinterpret_cast<T&>(*this);
+		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 {
-		std::type_index from = get_class(), to = class_of<T>();
-		if ( from != to ) throw bad_cast{ from, to };
-		return reinterpret_cast<const T&>(*this);
+		const T* p = dynamic_cast<const T*>(this);
+		if ( !p ) throw bad_cast{ get_class(), class_of<T>() };
+		return *p;
 	}
 
@@ -49,19 +52,10 @@
 };
 
-template<typename T>
-T* as_subclass_of( object* o ) {
-	T* r = dynamic_cast<T*>( o );
-	if ( r == nullptr ) throw bad_cast{ o->get_class(), class_of<T>() };
-	return r;
+template<typename To, typename From>
+std::unique_ptr<To> as_ptr( std::unique_ptr<From>&& p ) {
+	return std::unique_ptr<To>{ &p.release()->template as<To>() };
 }
 
-template<typename T>
-const T* as_subclass_of( const object* o ) {
-	const T* r = dynamic_cast<const T*>( o );
-	if ( r == nullptr ) throw bad_cast{ o->get_class(), class_of<T>() };
-	return r;
-}
-
-class ordered : public object {
+class ordered : public virtual object {
 public:
 	virtual int cmp(const ordered&) const = 0;
@@ -80,6 +74,10 @@
 };
 
-class boolean : public ordered {
-private:
+class printable : public virtual object {
+public:
+	virtual void print(std::ostream&) const = 0;
+};
+
+class boolean : public ordered, public printable {
 	bool x;
 
@@ -98,9 +96,5 @@
 	}
 
-	object& operator= (const object& that) override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return *this = static_cast<const boolean&>(that);
-	}
+	object& operator= (const object& that) override { return *this = that.as<boolean>(); }
 
 	~boolean() override = default;
@@ -108,17 +102,10 @@
 	int cmp(const boolean& that) const { return x == that.x ? 0 : x == false ? -1 : 1; }
 
-	// bool operator< (const boolean& that) const { return x < that.x; }
-
-	// bool operator== (const boolean& that) const { return x == that.x; }
-
-	int cmp(const ordered& that) const override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return cmp( static_cast<const boolean&>(that) );
-	}
-};
-
-class character : public ordered {
-private:
+	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;
 
@@ -137,9 +124,5 @@
 	}
 
-	object& operator= (const object& that) override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return *this = static_cast<const character&>(that);
-	}
+	object& operator= (const object& that) override { return *this = that.as<character>(); }
 
 	~character() override = default;
@@ -147,17 +130,13 @@
 	int cmp(const character& that) const { return x == that.x ? 0 : x < that.x ? -1 : 1; }
 
-	// bool operator< (const character& that) const { return x < that.x; }
-
-	// bool operator== (const character& that) const { return x == that.x; }
-
-	int cmp(const ordered& that) const override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return cmp( static_cast<const character&>(that) );
-	}
-};
-
-class integer : public ordered {
-private:
+	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;
 
@@ -176,9 +155,5 @@
 	}
 
-	object& operator= (const object& that) override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return *this = static_cast<const integer&>(that);
-	}
+	object& operator= (const object& that) override { return *this = that.as<integer>(); }
 
 	~integer() override = default;
@@ -186,17 +161,34 @@
 	int cmp(const integer& that) const { return x == that.x ? 0 : x < that.x ? -1 : 1; }
 
-	// bool operator< (const integer& that) const { return x < that.x; }
-
-	// bool operator== (const integer& that) const { return x == that.x; }
-
-	int cmp(const ordered& that) const override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return cmp( static_cast<const integer&>(that) );
-	}
-};
-
-class pair : public ordered {
-private:
+	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) {}
+
+	std::unique_ptr<object> new_inst() const override { return std::make_unique<c_string>(); }
+
+	std::unique_ptr<object> new_copy() const override { return std::make_unique<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() override = default;
+
+	void print(std::ostream& out) const override { out << s; }
+};
+
+class pair : public ordered, public printable {
 	std::unique_ptr<object> x;
 	std::unique_ptr<object> y;
@@ -220,30 +212,22 @@
 	}
 
-	object& operator= (const object& that) override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return *this = static_cast<const pair&>(that);
-	}
+	object& operator= (const object& that) override { return *this = that.as<pair>(); }
 
 	~pair() override = default;
 
 	int cmp(const pair& that) const {
-		const ordered* a = as_subclass_of<ordered>( x.get() );
-		const ordered* b = as_subclass_of<ordered>( that.x.get() );
-		int c = a->cmp( *b );
+		int c = x->as<ordered>().cmp( that.x->as<ordered>() );
 		if ( c != 0 ) return c;
-		a = as_subclass_of<ordered>( y.get() );
-		b = as_subclass_of<ordered>( that.y.get() );
-		return a->cmp( *b );
-	}
-
-	// bool operator< (const pair& that) const { return cmp(that) < 0; }
-
-	// bool operator== ( const pair& that) const { return cmp(that) == 0; }
-
-	int cmp(const ordered& that) const override {
-		std::type_index from = that.get_class(), to = get_class();
-		if ( from != to ) throw bad_cast{ from, to };
-		return cmp( static_cast<const pair&>(that) );
-	}
-};
+		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 << "]";
+	}
+};
