Compile Time Data Structure Using Template Meta Programming

1. Introduction

You have seen different applications of template meta-programming such as static data structures [5], algorithms [2][6], design patterns [1][3], reflection [4], expression templates [7], and number theory [8][9]. The Compile Time Data Structure is, in fact, not a new concept in C++. Further information about it can be found [1][5]. Here, you are going to study the link list as an example of a compile-time data structure and try to implement it with template meta-programming.

Template meta-programming is usually difficult to understand at first for those who are not familiar with it; therefore, you will learn about the run-time counterpart at the same time. You used a naming convention, "List," for all the runtime programs to distinguish with the compile-time version. Although you can implement the run-time programs in more than one way, in the compile-time world you have only recursion to do everything, including looping; therefore, you are going to use recursion in the run-time version too.

2. Compile Time Link List

If you are going to make a single link list, the structure of your link list would be something like this.

///////////////////////////////////////////////////////////
// Node of Runtime Link List
///////////////////////////////////////////////////////////
struct ListNode
{
   int value;
   ListNode* next;
};

Here is the compile-time version of this structure:

///////////////////////////////////////////////////////////
// Termination of Link List
///////////////////////////////////////////////////////////
struct End
{
};

///////////////////////////////////////////////////////////
// Node of Static Link List
///////////////////////////////////////////////////////////
template <int iData, typename Type>
struct Node
{
   enum { value = iData };
   typedef Type Next;
};

Here, you need one more structure to indicate the termination condition of the link list. You also can say it is an end marker. In the run-time version, you don't need any such concept because, in that case, you simply check the value of next field in the node. If its value is NULL, it means that it is the last node of the link list.

However, in the case of template meta-programming you have to do template specialization (or partial template specialization) to stop the recursion. You can do template specialization for any specific type or for any specific value. Here, you can't do template specialization on the value because the second template parameter is type. Therefore, you have to create one new type to stop the recursive instantiation of the template. The name of the end marker can be anything and it can be stored whatever you like. In the example, it would be sufficient to make one empty structure to create a new type as an end marker.

Now, try to implement a few auxiliary functions that work on lists. Here is the first function to insert data in the link list. You explicitly made its name look familiar to the member function of STL list class because you are going to implement a few more STL algorithms.

Here is the simplest implementation of how to insert items in a run-time single link list.

///////////////////////////////////////////////////////////
// Insert item into Runtime Link List
///////////////////////////////////////////////////////////
void ListPushBack(ListNode** pHead, int value)
{
   if (*pHead == NULL)
   {
      *pHead = new ListNode();
      (*pHead)->value = value;
   }
   else
   {
      ListPushBack(&(*pHead)->next, value);
   }
}

The name of the link list is prefixed by the "List" word to distinguish it from the compile-time version. Interestingly, the compile-time version of the same function doesn't use recursion and its implementation is very easy.

///////////////////////////////////////////////////////////
// Insert item into Static Link List
///////////////////////////////////////////////////////////
template <int iData, typename Type>




struct PushBack
{
   typedef Node<iData, Type> staticList;
};

And here is the usage of this function at compile time.

typedef PushBack<2,  End>::staticList   node1;
typedef PushBack<4,  node1>::staticList node2;
typedef PushBack<6,  node2>::staticList node3;
typedef PushBack<8,  node3>::staticList node4;
typedef PushBack<10, node4>::staticList node5;
typedef PushBack<12, node5>::staticList myList;

You can create a static link list in this way:

typedef Node<-15, Node<15, Node<18, Node<13, End> > > > myList;

But the above method to create a static link list has few advantages. You will see these when you implement a few STL algorithms at compile time.

Now, implement another STL list algorithm at compile time, but first, take a look at its run-time version to better understand template meta-programming.

///////////////////////////////////////////////////////////
// Structure to calculate the length of Run-time Link List
///////////////////////////////////////////////////////////
int ListSize(ListNode* pHead)
{
   if (pHead == NULL)
      return 0;
   else
      return 1 + ListSize(pHead->next);
}

This function is quite simple and uses tail recursion for optimization. The compiler can optimize tail recursion with looping to avoid any run-time stack overhead. Here is the compile-time version of the same function.

///////////////////////////////////////////////////////////
// Structure to calculate the length of Static Link List
///////////////////////////////////////////////////////////
template <typename T>
struct Size;

template <int iData, typename Type>
struct Size<Node<iData, Type> >
{
   enum { value = 1 + Size<Type>::value };
};

template <>
struct Size<End>
{
   enum { value = 0 };
};

Although the STL list class doesn't have an at() function because list doesn't have a random access iterator, you are trying to implement this function for a link list. Because you can't access any item of the link list randomly, it is a linear time function, not a constant time, just like the at() function of a vector.

Here is the simple run-time implementation of an at() function on a single link list with linear complexity.

///////////////////////////////////////////////////////////
// Structure to find item from specific location from
// Runtime Link List
///////////////////////////////////////////////////////////
int ListAt(ListNode* pHead, int iPos)
{
   static int iIndex = 0;
   ++iIndex;

   if (iIndex == iPos)
      return pHead->value;
   else if (pHead->next == NULL)
      return -1;
   else
      return ListAt(pHead->next, iPos);
}

The code presented here is just a proof of concept, not production-quality code. One major problem with this function is the return code. If the input position is greater than the length of link list, such as the length of the link list is 4 but you are trying to access the 6th element, this function returns -1. This code is misleading because -1 can be a datum in the link list at a given position so it would be impossible to differentiate whether it is an error code or the actual data at a given position.

This one is a much better implementation than the previous one.

///////////////////////////////////////////////////////////
// Structure to find item from specific location from
// Runtime Link List
///////////////////////////////////////////////////////////
bool ListAt(ListNode* pHead, int iPos, int* iVal)
{
   static int iIndex = 0;
   ++iIndex;

   if (iIndex == iPos)
   {
      *iVal = pHead->value;
      return true;
   }
   else if (pHead->next == NULL)
   {
      return false;
   }
   else
   {
      return ListAt(pHead->next, iPos, iVal);
   }
}

This function returns the value at a specific location by parameter. If the user passes a position that is greater than the length of the link list, it will return false; otherwise, it stores the value at the parameter and returns true.

Here is the usage of this function.

if (pHead != NULL)
{
   int val;

   if (ListAt(pHead, 3, ∓val))
   {
      std::cout << val << std::endl;
   }
}

Here is the compile-time version of the same function.


///////////////////////////////////////////////////////////
// Structure to find item from specific location from
// Static Link List
///////////////////////////////////////////////////////////
template <int iIndex, typename T, int iStart = 0>
struct At;

template <int iIndex, int iData, typename Type, int iStart>
struct At<iIndex, Node<iData, Type>, iStart>
{
   enum { value = iIndex == iStart ? iData : At<iIndex, Type,
      (iStart + 1)>::value };
};

template <int iIndex, int iData, int iStart>
struct At<iIndex, Node<iData, End>, iStart>
{
   enum { value = iIndex == iStart ? iData : -1 };
};

This program has the same problem; it returns -1 when an item is not found. In template meta-programming, you can't return a value by parameter, just like its run-time equivalent. The solution is to introduce one more enum variable inside the structure to store whether or not the item was found. Here is the next version of the same program.

///////////////////////////////////////////////////////////
// Structure to find item from specific location from
// Static Link List
///////////////////////////////////////////////////////////
template <int iIndex, typename T, int iStart = 0>
struct At;

template <int iIndex, int iData, typename Type, int iStart>
struct At<iIndex, Node<iData, Type>, iStart>
{
   enum { value = iIndex == iStart ? iData : At<iIndex, Type,
      (iStart + 1)>::value };
   enum { found = iIndex == iStart ? 1 : At<iIndex, Type,
      (iStart + 1)>::found };
};

template <int iIndex, int iData, int iStart>
struct At<iIndex, Node<iData, End>, iStart>
{
   enum { value = iIndex == iStart ? iData : -1 };
   enum { found = iIndex == iStart ? 1 : 0 };
};

Although the value variable still stores -1 when an item is not found in the link list, if you use the other variable—the "found" variable—you can ignore this value. Here is the usage of this algorithm.

if (At<8, myList>::found == 1)
{
   std::cout << At<8, myList>::value << std::endl;
}

Compile Time Data Structure Using Template Meta Programming

3. Compile Time Algorithms

In this section, you are going to study different STL algorithms, their working with the STL list class, and how to implement those at compile time using template meta-programming. For now, instead of using your own create Single Link List and implementing all related functions yourself, you are going to use the STL list class.

3.1. Find Algorithm

Find algorithm returns the first occurrence of the specified value in the given range. If it couldn't find the specified value, it returns the end iterator—one past the last element given range. Here is the simple usage of the find algorithm on the STL list.

std::list<int> lst;

lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(28);
lst.push_back(35);

std::list<int>::iterator iter_ = std::find(lst.begin(),
   lst.end(), 7);

if (iter_ != lst.end())
   std::cout << *iter_ << std::endl;
else
   std::cout << "Not found" << std::endl;

Here is the closest implementation of the find algorithm in template meta-programming.

///////////////////////////////////////////////////////////
// Structure to find the location of specific item in
// the Static Link List
///////////////////////////////////////////////////////////
template <typename TBegin,
          typename TEnd,
          int iFindData,
          int iStart = 0>
struct Find;

template <int iData,
          typename TBegin,
          typename TEnd,
          int iFindData,
          int iStart>
struct Find<Node<iData, TBegin>, TEnd, iFindData, iStart>
{
   enum { value = iFindData == iData ? iStart :
      Find<TBegin, TEnd, iFindData, (iStart + 1)>::value };
};

template <int iData,
          typename TEnd,
          int iFindData,
          int iStart>
struct Find<Node<iData, TEnd>, TEnd, iFindData, iStart>
{
   enum { value = iFindData == iData ? iStart : -1 };
};

template <typename TEnd, int iFindData>
struct Find<TEnd, TEnd, iFindData>
{
   enum { value = -1 };
};

This implementation returns the position of the specified value, if found in the given range of single link list; otherwise, it returns -1. Here is the usage of the above implementation.

typedef PushBack<7,  End>::staticList node1;
typedef PushBack<14, node1>::staticList node2;
typedef PushBack<21, node2>::staticList node3;
typedef PushBack<28, node3>::staticList node4;
typedef PushBack<35, node4>::staticList myList;

std::cout << Find<myList, End, 7>::value << std::endl;

You can even pass the range, just like the STL algorithms, in it to find elements in between the given range.

std::cout << Find<node3, node1, 7>::value << std::endl;

This is one reason to use the Static version of the PushBack implementation rather than create a whole Static Link list in one statement. By using the compile-time version of the PushBack implementation, you can get the iterator equivalent at compile time.

3.2. Count Algorithm

This algorithm returns the number of items in a given range that has the given value. Here is a sample to demonstrate this.

std::list<int> lst;

lst.push_back(7);
lst.push_back(14);
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(7);

std::cout << std::count(lst.begin(), lst.end(), 7) << std::endl;

The output of this program is 3, because 7 exist three times in the given range. Here is a similar algorithm implemented at compile time.

///////////////////////////////////////////////////////////
// Count Algorithm. Returns the number of elements in
// Static Link List
///////////////////////////////////////////////////////////
template <typename TBegin,
          typename TEnd,
          int iVal>
struct Count;

template <int iData,
          typename TBegin,
          typename TEnd,
          int iVal>
struct Count<Node<iData, TBegin>, TEnd, iVal>
{
   enum { value = (iData == iVal ? 1 : 0) +
      Count<TBegin, TEnd, iVal>::value };
};

template <int iData, typename TEnd, int iVal>
struct Count<Node<iData, TEnd>, TEnd, iVal>
{
   enum { value = iData == iVal ? 1 : 0 };
};

template <typename TEnd, int iVal>
struct Count<TEnd, TEnd, iVal>
{
   enum { value = 0 };
};

This implementation is very similar to the "Find" algorithm and its usage is also similar to that. Here is the closest implementation of a compile-time version of the same code you saw in this section using STL "count" algorithm.

typedef PushBack<7,  End>::staticList node1;
typedef PushBack<14, node1>::staticList node2;
typedef PushBack<7,  node2>::staticList node3;
typedef PushBack<14, node3>::staticList node4;
typedef PushBack<21, node4>::staticList node5;
typedef PushBack<7,  node5>::staticList myList;

std::cout << Count<myList, End, 7>::value << std::endl;

Just as with the "Find" algorithm, you also can count the number of specified items in a given range other than search in the complete static link list.

3.3. Find If Algorithm

If you want to find whether any link list contains any item that is greater than 10 but less than 20, you couldn't do it with the find algorithm. Here is the solution of this problem, the "find_if" algorithm; this algorithm returns the iterator of the element in the link list that satisfies the condition in the given predicate. A predicate can be a simple function or a function object; in other words, a class with an overloaded () operator returns true. In that function (or function object), you can check any condition you want. Take a look at the run-time version to better understand it before going to the compile-time version.

std::list<int> lst;

lst.push_back(7);
lst.push_back(14);
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(7);

std::list<int>::iterator iter_ =  std::find_if(lst.begin(),
   lst.end(), MyPredicate());

if (iter_ != lst.end())
   std::cout << *iter_ << std::endl;
else
   std::cout << "Not found" << std::endl;

Here, you pass MyPridicate as a function object. Here is its implementation.

struct MyPredicate
{
   bool operator()(int val)
   {
      return val > 10 && val < 20 ? true : false;
   }
};

Implementing the compile-time version of the "find_if" algorithm is not very difficult; it looks very similar to the "find" algorithm except for one addition to the predicate type parameter. Here is the compile time version of the "find if" algorithm.

///////////////////////////////////////////////////////////
// Find If Algorithm.
// Locate the specific items in Static Link List that
// satisfies the predicate
///////////////////////////////////////////////////////////
template <typename TBegin,
          typename TEnd,
          template <int iData> class Predicate,
          int iStart = 0>
struct FindIf;

template <int iData,
          typename TBegin,
          typename TEnd,
          template <int iData> class Predicate,
          int iStart>
struct FindIf<Node<iData, TBegin>, TEnd, Predicate, iStart>
{
   enum { value = Predicate<iData>::value == 1 ? iStart : 
      FindIf<TBegin, TEnd, Predicate, (iStart+1)>::value };
};

template <int iData,
          typename TEnd,
          template <int iData> class Predicate,
          int iStart>
struct FindIf<Node<iData, TEnd>, TEnd, Predicate, iStart>
{
   enum { value = Predicate<iData>::value == 1 ? iStart : -1 };
};

template <typename TEnd,
   template <int iData> class Predicate>
struct FindIf<TEnd, TEnd, Predicate>
{
   enum { value = -1 };
};

Here, you use the template-template parameter to make it easy to call a similar to STL algorithm. Here is the usage of this algorithm.

typedef PushBack<7,  End>::staticList node1;
typedef PushBack<14, node1>::staticList node2;
typedef PushBack<7,  node2>::staticList node3;
typedef PushBack<14, node3>::staticList node4;
typedef PushBack<21, node4>::staticList node5;
typedef PushBack<7,  node5>::staticList myList;


std::cout << FindIf<myList, End, MyPredicate>::value << std::endl;

In the compile-time world, the predicate is a structure (or class), not a function or function object. Here is the compile-time version of same predicate you used earlier in this section for the run-time version.

template <int val>
struct MyPredicate
{
   enum { value = val > 10 && val < 20 ? 1 : 0 };
};

Compile Time Data Structure Using Template Meta Programming

3.4. Count If Algorithm

The "count if" algorithm is a cousin of "find if". The only difference is that it will return the number of elements in the link list that satisfy the condition given in the predicate. Take a look at the run-time version of this algorithm first.

std::list<int> lst;

lst.push_back(7);
lst.push_back(14);
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(7);


std::cout << std::count_if(lst.begin(), lst.end(),
   MyPredicate()) << std::endl;

You use the same predicate that you made earlier.

struct MyPredicate
{
   bool operator()(int val)
   {
      return val > 10 && val < 20 ? true : false;
   }
};

The output of this program is 2 because there are two elements in the list that are greater than 10 and less than 20. Now, take a look at the compile-time version of the same algorithm.

///////////////////////////////////////////////////////////
// Count If Algorithm.
// Returns the number of elements in Static Link List that
// satisfies the predicate condition
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd, 
   template <int iData> class Predicate>
struct CountIf;

template <int iData, typename TBegin, typename TEnd,
          template <int iData> class Predicate>
struct CountIf<Node<iData, TBegin>, TEnd, Predicate>
{
   enum { value = (Predicate<iData>::value) + CountIf<TBegin,
          TEnd, Predicate>::value };
};

template <int iData, typename TEnd,
          template <int iData> class Predicate>
struct CountIf<Node<iData, TEnd>, TEnd, Predicate>
{
   enum { value = Predicate<iData>::value };
};

template <typename TEnd, 
   template <int iData> class Prediate>
struct CountIf<TEnd, TEnd, Prediate>
{
   enum { value = 0 };
};

This implementation is very similar to the "FindIf" algorithm; the only difference is that here you are actually counting the number of elements that satisfy the condition given in predicate rather than finding it. Here is the usage of this algorithm.

typedef PushBack<7,  End>::staticList node1;
typedef PushBack<14, node1>::staticList node2;
typedef PushBack<7,  node2>::staticList node3;
typedef PushBack<14, node3>::staticList node4;
typedef PushBack<21, node4>::staticList node5;
typedef PushBack<7,  node5>::staticList myList;

std::cout << CountIf<myList, End, MyPredicate>::value << std::endl;

3.5. Min, Max Algorithm

Although the STL version of the min and max algorithm doesn't work on a range, for consistency you implemented both algorithms in a similar way: You can find the maximum or minimum value of a static link list in a given range. Here are the simple implementations of both algorithms.

///////////////////////////////////////////////////////////
// Structure to find the Maximum value in the Link List
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd>
struct Max;

template <int iData, typename TBegin, typename TEnd>
struct Max<Node<iData, TBegin>, TEnd >
{
   enum { value = iData > Max<TBegin, TEnd>::value ? iData :

          Max<TBegin, TEnd>::value };
};

template <int iData, typename TEnd>
struct Max<Node<iData, TEnd>, TEnd>
{
   enum { value = iData };
};
///////////////////////////////////////////////////////////
// Structure to find the Minimum value in the Link List
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd>
struct Min;

template <int iData, typename TBegin, typename TEnd>
struct Min<Node<iData, TBegin>, TEnd>
{
   enum { value = iData < Min<TBegin, TEnd>::value ? iData :
          Min<TBegin, TEnd>::value };
};

template <int iData, typename TEnd>
struct Min<Node<iData, TEnd>, TEnd>
{
   enum { value = iData };
};

And here is the simple usage of these algorithms.

std::cout << Max<myList, End>::value << std::endl;
std::cout << Min<myList, End>::value << std::endl;

If you use the same static link list that you created in the previous section, you will get 21 and 7 at the output as a maximum and minimum value of static link list respectively. Just like other algorithms, here you also can specify the range to get the maximum or minimum value in a given range, not in complete static link list.

3.6. For Each Algorithm

Until now, all the algorithms you learned about are just getting information from the link list; none of the above algorithms change any value in the link list. This is because once you have some value, it is very difficult, if not impossible, to change the value that you assigned at compile time. Now, you will learn about one algorithm that performs some operation on the values of the link list, but here you explicitly restrict yourself to not change any value in the link list. Take a look at the simple example of the "for_each" algorithm to print the elements of the link list.

std::for_each(lst.begin(), lst.end(),PrintFunctionObject());

Here, "PrintFunctionObject" is your function object to do the actual work. Here is one of the simple implementations of this predicate.

struct PrintFunctionObject
{
   void operator()(int val)
   {
      std::cout << val << std::endl;
   }
};

Remember, it also can be a simple function instead of a function object. Here is its compile-time equivalent version of the same algorithm.

///////////////////////////////////////////////////////////
// For each algorithm. Apply some action on all items of
// the list
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd, 
          template <typename T> class Function>
struct ForEach;

template <int iData, typename TBegin, typename TEnd,
          template <typename T> class Function>
struct ForEach<Node<iData, TBegin>, TEnd, Function>
{
   void operator ()()
   {
      Function<TBegin>()(iData);
      ForEach<TBegin, TEnd, Function>()();
   }
};

template <int iData, typename TEnd,
          template <typename T> class Function>
struct ForEach<Node<iData, TEnd>, TEnd, Function>
{
   void operator ()()
   {
      Function<TEnd>()(iData);
   }
};

template <typename TEnd,
          template <typename T> class Function>
struct ForEach<TEnd, TEnd, Function>
{
   void operator ()()
   {
   }
};

If you look at this implementation closely, you may already have noticed that this implementation is quite different than all of those that were discussed earlier. In this implementation, there isn't any "enum" variable; on the contrary, here you overload the () operator. When you have to perform some action, you have to call some function to do the actual work. This program is actually a mixture of run-time and compile-time programming. All of the programs you saw earlier have beencompletely resolved by the compiler at the time of compiling and the actual result will be stored in the executable.

However, in this case, although you are doing compile-time computation during the compilation using recursion, still the actual function call will execute when you will run this program. This is the only way to perform some IO operation while doing template meta-programming—using functions that will execute at run time. Here, you are not only limited to perform IO operation but you can do few more interesting things, such as create a class using a new operator or set of class hierarchies, as described in "Modern C++ Design"[1]. Here is the function object for this compile-time implementation of the "For each" algorithm.

template <typename T>
struct PrintFunctonObject
{
   void operator ()(int iData)
   {
      std::cout << iData << std::endl;
   }
};

The usage of this algorithm is also slightly different. Here, you actually want to execute some code at run time; therefore, you have to create an object of the "ForEach" structure. Here is one possible way to execute this algorithm using an anonymous object.

ForEach<myList, End, PrintFunctonObject>()();

4. Future Work

Here, you learned how you can implement the link list and a few of its related algorithms and compile-time versions. This work can be enhanced to use other data structures too and include more algorithms. The other dimension where you can enhance this work is functional programming approach, which I barely touched here without even mentioning its name. You discovered only one approach to mangle the compile-time and run-time execution while implementing a "ForEach" algorithm. This technique can be used to implement some more powerful and interesting concepts.

5. References

  1. Modern C++ Design Generic Programming. Andrei Alexandrescu
  2. C++ Template Metaprogramming: Concepts, Tools and Techniques from Boost and Byond. David Abrahams, Aleksey Gurtovoy. Addison Wesley, 2004
  3. Making Patterns Explicit with Meta-programming. Daniel von Dincklage. Proceedings of the second international conference on Generative programming and component engineering (2003)
  4. Reflection Support by means of template Meta-programming. Guiseppe Attardi, Antonio Cisternino. http://lcgapp.cern.ch/project/architecture/ReflectionPaper.pdf
  5. Static Data Structure: Reconciling Template Meta-programming and Generic Programming. Michael C. Burton, William G. Griswold, Andrew D. McCulloch, Gray A. Huber. http://www.cs.ucsd.edu/~wgg/Statements/mburton-ifip-gw-07-2002.pdf
  6. Loops, Metaloops, & C++. Roshan Naik. August 2004 Dr Dobb Journal. http://www.ddj.com/cpp/184401835
  7. Expression Templates. Veldhuizen. C++ Report 1995. Generative Programming—Methods, Tools and Applications
  8. Template Meta Programming and Number Theory. Zeeshan Amjad. http://www.codeguru.com/cpp/misc/misc/math/article.php/c14087
  9. Template Meta Programming and Number Theory: Part 2. Zeeshan Amjad. http://www.codeguru.com/cpp/misc/misc/math/article.php/c14213


About the Author

Zeeshan Amjad

C++ Developer at Bechtel Corporation. zamjad.wordpress.com

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Due to internal controls and regulations, the amount of long term archival data is increasing every year. Since magnetic tape does not need to be periodically operated or connected to a power source, there will be no data loss because of performance degradation due to the drive actuator. Read this white paper to learn about a series of tests that determined magnetic tape is a reliable long-term storage solution for up to 30 years.

Most Popular Programming Stories

More for Developers

RSS Feeds