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);
}
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)
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With