C++ template - using "std::is_same_v" instead of specializing and avoid compilation error?

14,796

Solution 1

To answer your question about templates (although in this particular application, it is not the right solution, for many reasons):

The reason it doesn't work as you wrote it is that template instantiation happens at compile-time, and the only thing happening then is that the value of std::is_same is computed for the template argument. Thus, in the code for function<solo> the line

if(std::is_same<T, duo>::value) std::cout<< std::to_string(test.b);

would be like

if(false) std::cout<< std::to_string(test.b);

which doesn't compile as there is no member b in test.

To make it work, you need two templates and use SFINAE to select the correct one when instantiating the template (and as function templates cannot be partially specialized, you need to write it something like the following, which is really a silly way of writing two overloads. Or you can fully specialize the template, but then you wouldn't use if_same).

template<class T>
typename std::enable_if<!std::is_same<T, duo>::value, void>::type function(T test){
 std::cout<< std::to_string(test.a);
}

template<class T>
typename std::enable_if<std::is_same<T, duo>::value, void>::type function(T test){
 std::cout<< std::to_string(test.a);
 std::cout<< std::to_string(test.b);
}

Further, note that is_same looks at the static type of the variable, so if you have a solo& to a duo object, it would still choose the solo overload.

A somewhat less silly use of templates is to write a function template that can handle any type that has a member int b. This uses a helper metafunction (a struct, so we can use partial specialization):

template <class T, class = int>
struct has_member_b : std::false_type {};

template <class T> 
struct has_member_b<T, decltype(std::declval<T>().b)> : std::true_type {};   

template<class T>
typename std::enable_if<has_member_b<T>::value, void>::type function(T test){
    std::cout<< std::to_string(test.a);
    std::cout<< std::to_string(test.b);
}

template<class T>
typename std::enable_if<!has_member_b<T>::value, void>::type function(T test) {
    std::cout<< std::to_string(test.a);
}

(Do note that both versions assume there to be a member a, if not it will not compile)

Solution 2

With the introduction of constexpr if(cond) in C++17 you can achieve your goal. constexpr if(cond) gets evaluated at compile time, hence you can choose what you want to do depending the type of the parameter. Following snippet provides an illustration.

#include <iostream>
#include <string>
#include <type_traits>

struct solo{
  int a;     
};

struct duo : solo{
    int b;
};

template<class T>
void function(T test){ 
 if constexpr (std::is_same<T, duo>::value) 
    std::cout<< std::to_string(test.b)<<"\n";
 else if constexpr (std::is_same<T, solo>::value) 
    std::cout<< std::to_string(test.a)<<"\n";
}

int main()
{
  solo test1;
  test1.a = 1;

  duo test2;
  test2.b = 2;

  function(test1);
  function(test2);
}

Solution 3

You don't need template for this since duo extends solo. Simply call function(solo) from function(duo):

void function(solo test) {
   std::cout << std::to_string(test.a);
}

void function(duo test) {
   function((solo) test);
   std::cout << std::to_string(test.b);
}
Share:
14,796

Related videos on Youtube

Hugo
Author by

Hugo

Updated on September 16, 2022

Comments

  • Hugo
    Hugo over 1 year

    I'm new to templates and I'm trying to use them in order to avoid duplicating functions that are very much alike.

    In the example below, I made a simple and small working example showing my issue.

    In particular, I have two struct ("solo" and "duo"). Those struct's have a common member (a) and one of them has a specific member (b).

    Then I have a template function that can take either struct and print the member a... and I wanted it to be able to print member b only if the struct type is "duo".

    The way I did (using std::is_same_v) it doesn't compile. I read that one can use specialization to do so, however I was wondering if there is not a more elegant way? Because then I have the feeling to loose the advantage of templates... but probably I don't get the power of templates yet and how/for what to use them.

    Thank you very much for your help!

    #include <iostream>
    #include <string>
    #include <type_traits>
    
    struct solo{
      int a;     
    };
    
    struct duo : solo{
        int b;
    };
    
    template<class T>
    void function(T test){
     std::cout<< std::to_string(test.a);
     if(std::is_same<T, duo>::value) std::cout<< std::to_string(test.b);
    }
    
    int main()
    {
      solo testS;
      testS.a = 1;
    
      function(testS);
    }
    
    • HolyBlackCat
      HolyBlackCat almost 6 years
      Use if constexpr instead of plain if. Or ditch templates and make two overloads of your function.
    • Some programmer dude
      Some programmer dude almost 6 years
      For this I would rather use polymorphism to print the output. The templated function calls a virtual function in the object to do the actual printing, and it can print whatever it wants or needs. The printing function for duo could even all the solo print function to print a. Especially if the inheritance tree is large or you have many child-classes.
  • Hugo
    Hugo almost 6 years
    Thanks for your answer. Now I have a better understanding of what was happening and how I could do. I will just go with "classic overload of functions" as suggested by Holt but I'm choosing your answer since it was the one that really helped me understand how this works. And thanks to everyone for your very prompt and kind answers:)
  • Kiruahxh
    Kiruahxh about 3 years
    How do you choose the return type of function ?
  • drRobertz
    drRobertz about 3 years
    The return type is the second argument of enable_if. Look up std::enable_if for details.