Recommand · October 14, 2021 0

How do I cast a C++ template parameter?

I have a C++ function that looks like this:

template <class T> void MyClass::set(T value) {
  if (std::is_same<T, std::string>::value) {
    stringValue_ = value;
  } else if (std::is_same<T, int>::value) {
    intValue_ = value;
  }
}

However, I’m getting compiler errors. Apparently it believes that the type of T is always a std::string:

assigning to 'int' from incompatible type 'std::__cxx11::basic_string<char>'

Additionally, when I try to cast the value to an int like (int)value, I get casting errors. The entire point of this function is to take in a template parameter value and then assign it to the correct variable of the class based on the parameter’s actual type. Please let me know which misconceptions or errors I’m making here.

You cannot cast a template parameter, in the manner you’re trying to do:

template <class T> void MyClass::set(T value) {
  if (std::is_same<T, std::string>::value) {
    stringValue_ = value;
  } else if (std::is_same<T, int>::value) {
    intValue_ = value;
  }
}

The key concept here is that a template function gets expanded in its entirety when it gets instantiated.

If say, for a given template instance, the T template parameter is an integer. This template then get instantiated in approximately the following manner:

void MyClass::set(int value)
{
     if (false)
     {
         stringValue_=value;
     }

Your attempt here to set stringValue, that’s presumably a std::string, to an int, is not going to be very successful. Just because the if condition is false, does not make this chunk of code go away. It still must be valid C++, even if it never gets executed, and this is not valid C++. This is the explanation for your compilation error.

Some features in the newest C++17 standard will make these kinds of constructs actually possible. However, pre-C++17 the general approach to solving this kind of a problem is to use template specialization:

template<typename T> void MyClass::set(T value);

template<> void MyClass::set<int>(int value)
{
    intValue_=value;
}

template<> void MyClass::set<std::string>(std::string value)
{
    stringValue_=value;
}

Or, forget templates entirely, and just define two separate set() methods.

The reason for the compiler error is that both branches of the if statement will be instantiated with the template. This is obviously an error because if T = int there is not assignment operator to std::string. C++17 introduces a method to disregard one of the branches at compile-time, called if constexpr:

template <class T> void MyClass::set(T value) {
  if constexpr (std::is_same<T, std::string>::value) {
    stringValue_ = value;
  } else if (std::is_same<T, int>::value) {
    intValue_ = value;
  }
}

But actually, don’t use a template here (and don’t use SFINAE here either!). Just use good old function overloading.

class MyClass
{
    std::string stringValue_;
    int intValue_;
public:
    void set(int);
    void set(std::string);
};

void MyClass::set(int value) {
    intValue_ = value;
}

void MyClass::set(std::string value) {
    stringValue_ = value;
}

One could argue that SFINAE is actually necessary if you are trying to cover a whole range of types which are convertible into each other.

class MyClass
{
    std::string stringValue_;
    int intValue_;
public:
    template < typename T, typename std::enable_if< std::is_arithmetic<T>::value, void** >::type = nullptr >
    void set(T value) {
        intValue_ = value;
    }

    template < typename T, typename std::enable_if< std::is_same<T,std::string>::value, void** >::type = nullptr >
    void set(T value) {
        stringValue_ = value;
    }
};