Index: src/AST/Decl.hpp
===================================================================
--- src/AST/Decl.hpp	(revision a056f56794da880f8c293f2c3c87427b6dfe1cb6)
+++ src/AST/Decl.hpp	(revision e5c38112cf0116f0fcdd3e08dd4133429b2ecfbe)
@@ -75,4 +75,11 @@
 	int scopeLevel = 0;
 
+	/*
+	ForallDecl foralls {
+		list<TypeDecl> params
+		list<ObjectDecl> assns
+	}
+	*/
+
 	std::vector<ptr<Attribute>> attributes;
 	Function::Specs funcSpec;
Index: src/AST/SymbolTable.cpp
===================================================================
--- src/AST/SymbolTable.cpp	(revision a056f56794da880f8c293f2c3c87427b6dfe1cb6)
+++ src/AST/SymbolTable.cpp	(revision e5c38112cf0116f0fcdd3e08dd4133429b2ecfbe)
@@ -95,5 +95,19 @@
 }
 
+SymbolTable::SpecialFunctionKind SymbolTable::getSpecialFunctionKind(const std::string & name) {
+	if (name == "?{}") return CTOR;
+	if (name == "^?{}") return DTOR;
+	if (name == "?=?") return ASSIGN;
+	return NUMBER_OF_KINDS;
+}
+
 std::vector<SymbolTable::IdData> SymbolTable::lookupId( const std::string &id ) const {
+	static Stats::Counters::CounterGroup * name_lookup_stats = Stats::Counters::build<Stats::Counters::CounterGroup>("Name Lookup Stats");
+	static std::map<std::string, Stats::Counters::SimpleCounter *> lookups_by_name;
+	static std::map<std::string, Stats::Counters::SimpleCounter *> candidates_by_name;
+
+	SpecialFunctionKind kind = getSpecialFunctionKind(id);
+	if (kind != NUMBER_OF_KINDS) return specialLookupId(kind);
+
 	++*stats().lookup_calls;
 	if ( ! idTable ) return {};
@@ -107,4 +121,67 @@
 		out.push_back( decl.second );
 	}
+
+	if (Stats::Counters::enabled) {
+		if (! lookups_by_name.count(id)) {
+			// leaks some strings, but it is because Counters do not hold them
+			auto lookupCounterName = new std::string(id + "%count");
+			auto candidatesCounterName = new std::string(id + "%candidate");
+			lookups_by_name.emplace(id, new Stats::Counters::SimpleCounter(lookupCounterName->c_str(), name_lookup_stats));
+			candidates_by_name.emplace(id, new Stats::Counters::SimpleCounter(candidatesCounterName->c_str(), name_lookup_stats));
+		}
+		(*lookups_by_name[id]) ++;
+		*candidates_by_name[id] += out.size();
+	}
+
+	return out;
+}
+
+std::vector<SymbolTable::IdData> SymbolTable::specialLookupId( SymbolTable::SpecialFunctionKind kind, const std::string & otypeKey ) const {
+	static Stats::Counters::CounterGroup * special_stats = Stats::Counters::build<Stats::Counters::CounterGroup>("Special Lookups");
+	static Stats::Counters::SimpleCounter * stat_counts[3] = {
+		Stats::Counters::build<Stats::Counters::SimpleCounter>("constructor - count", special_stats),
+		Stats::Counters::build<Stats::Counters::SimpleCounter>("destructor - count", special_stats),
+		Stats::Counters::build<Stats::Counters::SimpleCounter>("assignment - count", special_stats)
+	};
+
+	static Stats::Counters::SimpleCounter * stat_candidates[3] = {
+		Stats::Counters::build<Stats::Counters::SimpleCounter>("constructor - candidates", special_stats),
+		Stats::Counters::build<Stats::Counters::SimpleCounter>("destructor - candidates", special_stats),
+		Stats::Counters::build<Stats::Counters::SimpleCounter>("assignment - candidates", special_stats)
+	};
+
+	static Stats::Counters::SimpleCounter * num_lookup_with_key 
+		= Stats::Counters::build<Stats::Counters::SimpleCounter>("keyed lookups", special_stats);
+	static Stats::Counters::SimpleCounter * num_lookup_without_key 
+		= Stats::Counters::build<Stats::Counters::SimpleCounter>("unkeyed lookups", special_stats);
+
+	assert (kind != NUMBER_OF_KINDS);
+	++*stats().lookup_calls;
+	if ( ! specialFunctionTable[kind] ) return {};
+
+	std::vector<IdData> out;
+
+	if (otypeKey.empty()) { // returns everything
+		++*num_lookup_without_key;
+		for (auto & table : *specialFunctionTable[kind]) {
+			for (auto & decl : *table.second) {
+				out.push_back(decl.second);
+			}
+		}
+	}
+	else {
+		++*num_lookup_with_key;
+		++*stats().map_lookups;
+		auto decls = specialFunctionTable[kind]->find(otypeKey);
+		if (decls == specialFunctionTable[kind]->end()) return {};
+
+		for (auto decl : *(decls->second)) {
+			out.push_back(decl.second);
+		}
+	}
+
+	++*stat_counts[kind];
+	*stat_candidates[kind] += out.size();
+
 	return out;
 }
@@ -366,11 +443,16 @@
 namespace {
 	/// gets the base type of the first parameter; decl must be a ctor/dtor/assignment function
-	std::string getOtypeKey( const FunctionDecl * function ) {
-		const auto & params = function->type->params;
+	std::string getOtypeKey( const FunctionType * ftype, bool stripParams = true ) {
+		const auto & params = ftype->params;
 		assert( ! params.empty() );
 		// use base type of pointer, so that qualifiers on the pointer type aren't considered.
 		const Type * base = InitTweak::getPointerBase( params.front() );
 		assert( base );
-		return Mangle::mangle( base );
+		if (stripParams) {
+			if (dynamic_cast<const PointerType *>(base)) return Mangle::Encoding::pointer;
+			return Mangle::mangle( base, Mangle::Type | Mangle::NoGenericParams );
+		}
+		else
+			return Mangle::mangle( base );	
 	}
 
@@ -380,5 +462,5 @@
 			const DeclWithType * decl, const std::string & otypeKey ) {
 		auto func = dynamic_cast< const FunctionDecl * >( decl );
-		if ( ! func || otypeKey != getOtypeKey( func ) ) return nullptr;
+		if ( ! func || otypeKey != getOtypeKey( func->type, false ) ) return nullptr;
 		return func;
 	}
@@ -405,5 +487,5 @@
 	bool dataIsUserDefinedFunc = ! function->linkage.is_overrideable;
 	bool dataIsCopyFunc = InitTweak::isCopyFunction( function );
-	std::string dataOtypeKey = getOtypeKey( function );
+	std::string dataOtypeKey = getOtypeKey( function->type, false ); // requires exact match to override autogen
 
 	if ( dataIsUserDefinedFunc && dataIsCopyFunc ) {
@@ -577,4 +659,26 @@
 		const DeclWithType * decl, SymbolTable::OnConflict handleConflicts, const Expr * baseExpr,
 		const Decl * deleter ) {
+	SpecialFunctionKind kind = getSpecialFunctionKind(decl->name);
+	if (kind == NUMBER_OF_KINDS) { // not a special decl
+		addId(decl, decl->name, idTable, handleConflicts, baseExpr, deleter);
+	}
+	else {
+		std::string key;
+		if (auto func = dynamic_cast<const FunctionDecl *>(decl)) {
+			key = getOtypeKey(func->type);
+		}
+		else if (auto obj = dynamic_cast<const ObjectDecl *>(decl)) {
+			key = getOtypeKey(obj->type.strict_as<PointerType>()->base.strict_as<FunctionType>());
+		}
+		else {
+			assertf(false, "special decl with non-function type");
+		}
+		addId(decl, key, specialFunctionTable[kind], handleConflicts, baseExpr, deleter);
+	}
+}
+
+void SymbolTable::addId(
+		const DeclWithType * decl, const std::string & lookupKey, IdTable::Ptr & table, SymbolTable::OnConflict handleConflicts, const Expr * baseExpr,
+		const Decl * deleter ) {
 	++*stats().add_calls;
 	const std::string &name = decl->name;
@@ -607,11 +711,11 @@
 	// ensure tables exist and add identifier
 	MangleTable::Ptr mangleTable;
-	if ( ! idTable ) {
-		idTable = IdTable::new_ptr();
+	if ( ! table ) {
+		table = IdTable::new_ptr();
 		mangleTable = MangleTable::new_ptr();
 	} else {
 		++*stats().map_lookups;
-		auto decls = idTable->find( name );
-		if ( decls == idTable->end() ) {
+		auto decls = table->find( lookupKey );
+		if ( decls == table->end() ) {
 			mangleTable = MangleTable::new_ptr();
 		} else {
@@ -628,6 +732,6 @@
 						lazyInitScope();
 						*stats().map_mutations += 2;
-						idTable = idTable->set(
-							name,
+						table = table->set(
+							lookupKey,
 							mangleTable->set(
 								mangleName,
@@ -644,7 +748,9 @@
 	IdData data{ decl, baseExpr, deleter, scope };
 	// Ensure that auto-generated ctor/dtor/assignment are deleted if necessary
-	if ( ! removeSpecialOverrides( data, mangleTable ) ) return;
+	if (table != idTable) { // adding to special table
+		if ( ! removeSpecialOverrides( data, mangleTable ) ) return;
+	}
 	*stats().map_mutations += 2;
-	idTable = idTable->set( name, mangleTable->set( mangleName, std::move(data) ) );
+	table = table->set( lookupKey, mangleTable->set( mangleName, std::move(data) ) );
 }
 
Index: src/AST/SymbolTable.hpp
===================================================================
--- src/AST/SymbolTable.hpp	(revision a056f56794da880f8c293f2c3c87427b6dfe1cb6)
+++ src/AST/SymbolTable.hpp	(revision e5c38112cf0116f0fcdd3e08dd4133429b2ecfbe)
@@ -33,4 +33,8 @@
 class SymbolTable final : public std::enable_shared_from_this<ast::SymbolTable> {
 public:
+	/// special functions stored in dedicated tables, with different lookup keys
+	enum SpecialFunctionKind {CTOR, DTOR, ASSIGN, NUMBER_OF_KINDS};
+	static SpecialFunctionKind getSpecialFunctionKind(const std::string & name);
+
 	/// Stored information about a declaration
 	struct IdData {
@@ -77,4 +81,8 @@
 	UnionTable::Ptr unionTable;    ///< union namespace
 	TraitTable::Ptr traitTable;    ///< trait namespace
+	IdTable::Ptr specialFunctionTable[NUMBER_OF_KINDS];
+
+	// using SpecialFuncTable = PersistentMap< std::string, IdTable::Ptr >; // fname (ctor/dtor/assign) - otypekey
+	// SpecialFuncTable::Ptr specialFuncTable;
 
 	using Ptr = std::shared_ptr<const SymbolTable>;
@@ -95,4 +103,6 @@
 	/// Gets all declarations with the given ID
 	std::vector<IdData> lookupId( const std::string &id ) const;
+	/// Gets special functions associated with a type; if no key is given, returns everything
+	std::vector<IdData> specialLookupId( SpecialFunctionKind kind, const std::string & otypeKey = "" ) const;
 	/// Gets the top-most type declaration with the given ID
 	const NamedTypeDecl * lookupType( const std::string &id ) const;
@@ -186,4 +196,9 @@
 		const Decl * deleter = nullptr );
 
+	/// common code for addId when special decls are placed into separate tables
+	void addId(
+		const DeclWithType * decl, const std::string & lookupKey, IdTable::Ptr & idTable, OnConflict handleConflicts, 
+		const Expr * baseExpr = nullptr, const Decl * deleter = nullptr);
+	
 	/// adds all of the members of the Aggregate (addWith helper)
 	void addMembers( const AggregateDecl * aggr, const Expr * expr, OnConflict handleConflicts );
Index: src/AST/Type.cpp
===================================================================
--- src/AST/Type.cpp	(revision a056f56794da880f8c293f2c3c87427b6dfe1cb6)
+++ src/AST/Type.cpp	(revision e5c38112cf0116f0fcdd3e08dd4133429b2ecfbe)
@@ -216,4 +216,17 @@
 }
 
+bool isUnboundType(const Type * type) {
+	if (auto typeInst = dynamic_cast<const TypeInstType *>(type)) {
+		// xxx - look for a type name produced by renameTyVars.
+
+		// TODO: once TypeInstType representation is updated, it should properly check
+		// if the context id is filled. this is a temporary hack for now
+		if (std::count(typeInst->name.begin(), typeInst->name.end(), '_') >= 3) {
+			return true;
+		}
+	}
+	return false;
+}
+
 }
 
Index: src/AST/Type.hpp
===================================================================
--- src/AST/Type.hpp	(revision a056f56794da880f8c293f2c3c87427b6dfe1cb6)
+++ src/AST/Type.hpp	(revision e5c38112cf0116f0fcdd3e08dd4133429b2ecfbe)
@@ -274,6 +274,10 @@
 public:
 	using ForallList = std::vector<ptr<TypeDecl>>;
-
+	// using ForallList = std::vector<readonly<TypeDecl>>;
+
+	// using ForallList = std::vector<ptr<TypeInstType>>;
 	ForallList forall;
+	// using AssertionList = std::vector<ptr<VariableExpr>>;
+	// AssertionList assertions;
 
 	ParameterizedType( ForallList&& fs = {}, CV::Qualifiers q = {},
@@ -423,4 +427,5 @@
 public:
 	readonly<TypeDecl> base;
+	// int context;
 	TypeDecl::Kind kind;
 
@@ -535,5 +540,8 @@
 };
 
+bool isUnboundType(const Type * type);
+
 }
+
 
 #undef MUTATE_FRIEND
@@ -541,4 +549,5 @@
 // Local Variables: //
 // tab-width: 4 //
+
 // mode: c++ //
 // compile-command: "make install" //
