Hi, glad you have figured it out for the most part.
To answer your question about not needing to check isComplex() to decide to call extractReal or extractComplex, the way around this is rather than defining a function (or a lambda function) which by definition can only take a single set of arguments, you can define a function object: an object which has multiple overloads of the operator()(...) method.
So if you define (outside of your function, although inside may work too):
struct GetSize
{
size_t
operator()(DenseReal & d) { return d.size(); }
size_t
operator()(DenseCplx & d) { return d.size(); }
};
Then doing:
auto size = applyFunc(GetSize(),T.store());
However, your use case of wanting to get the pointer to the data falls outside of this approach more because of a C++ issue rather than an ITensor issue. The reason is that the pointer type would be different anyway between DenseReal and DenseCplx storage, so there can't be a common return type for the function returning the pointer. So you might as well call isComplex() and then specialize to a different function for each case.
(On a side note though, you may be able to define function objects whose operator() overloads return different types and still use them. I forget if I covered that case but I probably did. Try it out!)
Miles