Search code examples
c++templatestemplate-argument-deduction

Can I avoid explicitly writing out my class-template parameter-type during template instantiation?


I wrote a little templated RAII-class called SaveGuard whose constructor makes a copy of the current state of a specified object, and then later on the destructor restores the object from that saved state. That way I can make temporary changes to an object and I am guaranteed that they will be auto-reverted at the end of the current scope (see code below).

That all works fine; my question is this: Is there a way for me to avoid having to explicitly type out the type of the to-be-saved object every time I declare a SaveGuard? i.e. rather than typing this:

const SaveGuard<std::string> guard(myStr);

I'd prefer to type something like this:

const SaveGuard<> guard(myStr);

Since the type of the object might be rather elaborate, and I might be declaring SaveGuard objects for it in many places, it would save a lot of typing and declutter my code if I could.

However, trying that yields this error:

temp.cpp:23:17: error: too few template arguments for class template 'SaveGuard'
      const SaveGuard<> guard(myStr);

Code follows:

#include <iostream>
#include <string>

/** Convenience class to place on the stack for RAII-swapping of a table out to temporary storage and then back again in the destructor */
template<class T> class SaveGuard
{
public:
   SaveGuard(T & saveMe) : _saveMe(saveMe), _tempHolder(saveMe) {/* empty */}  // save the current value
   ~SaveGuard() {_saveMe = _tempHolder;}  // restore the saved value

private:
   T & _saveMe;
   T _tempHolder;
};

int main(int argc, char ** argv)
{
   std::string myStr = "foo";

   std::cout << "At point A, myStr=" << myStr << std::endl;

   {
      const SaveGuard<std::string> guard(myStr);

      // Make some temporary modifications to myStr
      myStr += "bar";
      std::cout << "At point B, myStr=" << myStr << std::endl;
   }

   std::cout << "At point C, myStr=" << myStr << std::endl;
   return 0;
}

When run, the code print out this:

At point A, myStr=foo
At point B, myStr=foobar
At point C, myStr=foo

Solution

  • In C++17, you can use class template argument deduction. This requires you to completely omit the template argument list:

    SaveGuard guard(myStr);  // OK - deduces T as std::string
    

    Before C++17, class template arguments cannot be deduced, so a helper function had to be used instead (std::make_pair is an example of this). You could write a MakeSaveGuard function that deduces the template argument from the function argument type (this requires SaveGuard to be properly movable).