The following is a minimal example of overloading a function on one parameter with canonical structures:
Axiom U C : Type.
Structure Op := {
sort : Type;
op : forall (_ : sort), Type
}.
Canonical Structure UOp := (Build_Op U (fun (_ : U) => C)). (* The functions are silly, nevermind *)
Canonical Structure COp := (Build_Op C (fun (_ : C) => U)).
Definition Op := fun {op_inst : Op} (x : (sort op_inst)) => (op op_inst x).
(* Note: 'Check' is *not* enough to verify correctness! *)
Definition UOp := fun (x : U) => (Op x).
Definition COp := fun (x : C) => (Op x).
This works great. However, the obvious extension to multiple parameters (adding an additional sort field) does not work. Neither does using a record or inductive type to pair two types (even if the final functions are of 1 parameter that is a pair!). In all cases coq finds a suitable unification for the first type, and if the second type doesn't match unification fails and no attempt to backtrack is made.
I've tried to nest structures to coax both unifications to happen simultaneously or to make them solvable sequentially, but haven't succeeded in figuring something that works.
To be clear, I'm looking for
Definition UUOp := fun (x y : U) => (Op x y).
Definition UCOp := fun (x : U) (y : C) => (Op x y).
Definition CUOp := fun (x : C) (y : U) => (Op x y).
Definition CCOp := fun (x y : C) => (Op x y).
to all unify and evaluate to different functions.
Typeclasses should be able to do this trivially but I'd prefer to avoid ad-hoc search if possible.