Yes I think the 2nd option could be what you need.
In lieu of a proper documentation of the storage system, here is a brief tour of how it works (using examples from the ITensor code):
* Make your new storage type (it doesn't have to do anything yet) and register it with the storage system: edit the file itensor/itdata/storage_types.h and follow the instructions in that file, then recompile the whole library.
* To make a custom ITensor with your new storage type do:
auto is = IndexSet(i1,i2,i3); //these are the indices it will have
auto dat = MyDataType(...); //make the storage type, it can be whatever type
auto T = ITensor(std::move(is),std::move(dat)); //create the ITensor with custom storage
* To see how the "doTask" system for calling methods on various storage types works, let's use the example of computing the norm and look at the Dense<Real> storage type.
1. First the norm(T) function is called, code at itensor_interface.ih line 806.
2. This function calls `doTask(NormNoScale{},T.store());` where T.store() is an "opaque" storage pointer i.e. we don't yet know what storage type the ITensor actually has.
3. The doTask function "unwraps" the storage pointer and discovers the storage type; this is handled automatically for you.
4. Assuming the actual storage type is Dense<Real>, the doTask(NormNoScale{},Dense<Real>) function is called, defined on line 125 of itensor/itdata/dense.cc.
5. This function recieves a Dense<Real> object as a const reference and can then compute the norm of this object as appropriate. doTask functions can return any type you want, as long as all overloads of doTask which take the same "task object" (such as NormNoScale, Contract, PlusEQ, etc.) all return the same type.
For another example of the doTask system at work look at this short article on the ITensor website:
http://itensor.org/docs.cgi?page=formulas/extractdense
Here is an article talking about the concept of the doTask system:
http://itensor.org/docs.cgi?page=articles/storage
One thing that is helpful about the ITensor storage system is that you don't have to define all possible operations on a new storage type for the code to compile and for you to begin using it. You can start by defining a few operations, such as doTask(NormNoScale, MyType), doTask(Contract,MyType) etc. and only use those to test. Then when things are working you can continue to define more operations like doTask(PlusEQ,MyType) etc.