Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

templates

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);
}
like image 927
Hugo Avatar asked May 30 '18 13:05

Hugo


2 Answers

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)

like image 65
drRobertz Avatar answered Oct 08 '22 21:10

drRobertz


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);
}
like image 30
yadhu Avatar answered Oct 08 '22 22:10

yadhu