# Extract the Storage of a Dense ITensor Dense ITensors store their data in an object of type `Dense` or `Dense`. These types are basically just a lightweight wrapper around a `std::vector` or a `std::vector`. Here is a definition of `Dense` which is equivalent to the one used in ITensor template class Dense { std::vector store; //There are also class methods such as //various constructors etc. //... }; Say we have the following ITensor auto i = Index(2,"i"); auto j = Index(3,"j"); auto k = Index(4,"k"); auto T = ITensor(i,j,k); T.randomize(); and we want to extract its data, which is stored internally in a `Dense` object. There are two ways to do this: 1. Using `applyFunc` to apply a function to T's storage 2. Using the `doTask` system to create functions "dynamically overloaded" on T's storage type _Note_: when working with the internal storage of an ITensor or IQTensor, it is important to remember that the true elements of an ITensor are defined by the product of its scale factor (accessible via the `.scale()` method) with one of the elements in the storage. ## Using applyFunc It is simple to extract the data as a `std::vector` using the function `applyFunc`, which first extracts the storage type of the ITensor then applies the provided function to it: auto extractReal = [](Dense const& d) { return d.store; }; auto v = applyFunc(extractReal,T.store()); In the code above, `extractReal` is a lambda function that takes a `Dense` (by const reference so it does not make a copy) and returns its storage, which is a `std::vector`. The call to `applyFunc` takes `extractReal` as its first argument, and takes T's "storage pointer" `T.store()` as its second. It performs some magic to unwrap the storage pointer and discover that it is of type `Dense` in order to successfully call `extractReal`. If the storage had been of a different type, then `applyFunc` would throw an exception. ## Using doTask A more low-level approach to manipulating ITensor storage is to define an overload of `doTask`. First we need to define "task objects" which label our tasks. struct ExtractReal {}; struct ExtractCplx {}; Next we define our overloads of `doTask` std::vector doTask(ExtractReal, Dense const& d) { return d.store; } std::vector doTask(ExtractCplx, Dense const& d) { return d.store; } Finally we call `doTask` on T's storage pointer, which will automatically unwrap its type and call the appropriate overload //If T has Dense storage: auto v = doTask(ExtractReal{},T.store()); //If T has Dense storage: auto v = doTask(ExtractCplx{},T.store());