C++ template - using "std::is_same_v" instead of specializing and avoid compilation error?
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);
}
Related videos on Youtube
Hugo
Updated on September 16, 2022Comments
-
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 almost 6 yearsUse
if constexpr
instead of plainif
. Or ditch templates and make two overloads of your function. -
Some programmer dude almost 6 yearsFor 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 thesolo
print function to printa
. Especially if the inheritance tree is large or you have many child-classes.
-
-
Hugo almost 6 yearsThanks 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 about 3 yearsHow do you choose the return type of
function
? -
drRobertz about 3 yearsThe return type is the second argument of
enable_if
. Look upstd::enable_if
for details.