Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 465
user1765354 Avatar asked Jul 24 '17 23:07

user1765354


People also ask

Can we pass Nontype parameters to templates?

Template classes and functions can make use of another kind of template parameter known as a non-type parameter. A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument.

What is a template parameter?

Explanation: A template parameter is a special kind of parameter that can be used to pass a type as argument.

How do you use template arguments in C++?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

Can template have default parameters?

Template parameters may have default arguments. The set of default template arguments accumulates over all declarations of a given template.


2 Answers

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.

like image 55
Sam Varshavchik Avatar answered Sep 29 '22 06:09

Sam Varshavchik


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;
    }
};
like image 26
Henri Menke Avatar answered Sep 29 '22 05:09

Henri Menke