Sometimes I turn inquisitive towards the type traits of modern c++. A more dig into glibc unfolds the mystery behind the implementation. So how does is_same work then?
See the below code. I have shamelessly copied libstdc++ code :-) for learning myself.
Find it here: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/type_traits
#include <iostream>
template<typename _Tp, _Tp __v>
struct integral_constant
{
static constexpr _Tp value = __v;
typedef _Tp value_type;
typedef integral_constant<_Tp, __v> type;
constexpr operator value_type() const noexcept { return value; }
};
/// The type used as a compile-time boolean with true value.
typedef integral_constant<bool, true> true_type;
/// The type used as a compile-time boolean with false value.
typedef integral_constant<bool, false> false_type;
/// is_same
template<typename, typename>
struct is_same
: public false_type {
is_same() { std::cout << "False Type" << std::endl;}
};
template<typename _Tp>
struct is_same<_Tp, _Tp>
: public true_type
{
is_same() { std::cout << "True Type" << std::endl;}
};
int main()
{
is_same<int,int> test1;
is_same<int,unsigned char> test2;
std::cout << is_same<int,int>::value << std::endl;
std::cout << is_same<int,unsigned char>::value << std::endl;
}
See the below code. I have shamelessly copied libstdc++ code :-) for learning myself.
Find it here: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/type_traits
#include <iostream>
template<typename _Tp, _Tp __v>
struct integral_constant
{
static constexpr _Tp value = __v;
typedef _Tp value_type;
typedef integral_constant<_Tp, __v> type;
constexpr operator value_type() const noexcept { return value; }
};
/// The type used as a compile-time boolean with true value.
typedef integral_constant<bool, true> true_type;
/// The type used as a compile-time boolean with false value.
typedef integral_constant<bool, false> false_type;
/// is_same
template<typename, typename>
struct is_same
: public false_type {
is_same() { std::cout << "False Type" << std::endl;}
};
template<typename _Tp>
struct is_same<_Tp, _Tp>
: public true_type
{
is_same() { std::cout << "True Type" << std::endl;}
};
int main()
{
is_same<int,int> test1;
is_same<int,unsigned char> test2;
std::cout << is_same<int,int>::value << std::endl;
std::cout << is_same<int,unsigned char>::value << std::endl;
}
The fact is that, is_same works on template specialization! Hurray! It has O(1) complexity. So how is that being achieved?
consider is_same<int,int>
As you know that modern c++ allows template specialization for particular cases. The same thing works here as well!
is_same<> with same datatype resolves to specialized template
template<typename _Tp>
struct is_same<_Tp, _Tp>
: public true_type
{
is_same() { std::cout << "True Type" << std::endl;}
};
which is inherited from true_type which itself is a struct integral_constant having value true and type bool. Note the static constexpr value which is evaluated in compile time i.e. no runtime overhead is seen. Also the weird template structure contains first argument as typename and second as value. These are somethings which even I did not knew are possible to write in this fashion.
Needless to say, is_same<> evaluating on different types is quite trivial template substitution and tracing on similar basis results in value = false.
I was scratching my head why static is required for the first member of integral_constant. I feel its due to the fact that is_same is structure and its value is constant throughout. static makes that each copy of is_same is referencing to same copy in memory for 'value' member. The other members do not consume memory anyway!
Most importantly, the output of program:
True Type
False Type
1
0
struct is_same<_Tp, _Tp>
: public true_type
{
is_same() { std::cout << "True Type" << std::endl;}
};
which is inherited from true_type which itself is a struct integral_constant having value true and type bool. Note the static constexpr value which is evaluated in compile time i.e. no runtime overhead is seen. Also the weird template structure contains first argument as typename and second as value. These are somethings which even I did not knew are possible to write in this fashion.
Needless to say, is_same<> evaluating on different types is quite trivial template substitution and tracing on similar basis results in value = false.
I was scratching my head why static is required for the first member of integral_constant. I feel its due to the fact that is_same is structure and its value is constant throughout. static makes that each copy of is_same is referencing to same copy in memory for 'value' member. The other members do not consume memory anyway!
Most importantly, the output of program:
True Type
False Type
1
0