Introduction
I'm just begining to reading and studying about SFINAE. In order to improve my understanding I've started trying things by myself.
So I've been wondering about a useful but yet simple way to use the SFINAE powerful trick and I ended thinking about a set of functions that calculates how many bytes occupies a given type; as long as we're dealing with simple types the solution is trivial:
template <typename T> size_t SizeOf(const T &t)
{
return sizeof(T);
};
This naive approximation would get the size of anything: 1 for char
, possibly 4 for int
, hopefully 4 for char[4]
and whatever for class PrettyAwesome
or struct AmazingStuff
including padding bytes. But, what about the dynamic memory managed by this types?
So I would check if the given type is a pointer type then, the total size would be the size of pointer plus the size of pointed memory (if any).
template <typename T> size_t SizeOf(const T &*t)
{
size_t Result = sizeof(t);
if (t)
{
Result += sizeof(T);
}
return Result;
};
Yes, at this point it seems that SFINAE isn't needed at all but, let's think about containers. The SizeOf
a container must be the sum of sizeof(container_type)
plus the sum of the size of each of its elements, this is where SFINAE enters:
template <typename T> size_t SizeOf(const T &t)
{
size_t Result = sizeof(t);
for (T::const_iterator i = t.begin(); i != t.end(); ++i)
{
Result += SizeOf(*i);
}
return Result;
};
In the above code, detect if tye T
type has a const_iterator
is needed, and it the container is a map an specialization for pairs is needed too.
Questions
Finally, the questions begins here: What have I tried and in what problems I'm stuck?
#include <type_traits>
#include <string>
#include <map>
#include <iostream>
#include <vector>
// Iterable class detector
template <typename T> class is_iterable
{
template <typename U> static char has_iterator(typename U::const_iterator *);
template <typename U> static long has_iterator(...);
public:
enum
{
value = (sizeof(has_iterator<T>(0)) == sizeof(char))
};
};
// Pair class detector
template <typename T> class is_pair
{
template <typename U> static char has_first(typename U::first_type *);
template <typename U> static long has_first(...);
template <typename U> static char has_second(typename U::second_type *);
template <typename U> static long has_second(...);
public:
enum
{
value = (sizeof(has_first<T>(0)) == sizeof(char)) && (sizeof(has_second<T>(0)) == sizeof(char))
};
};
// Pointer specialization.
template <typename T> typename std::enable_if<std::is_pointer<T>::value, size_t>::type SizeOf(const T &aValue)
{
size_t Result = sizeof(aValue);
if (aValue)
{
Result += sizeof(T);
}
return Result;
}
// Iterable class specialization.
template <typename T> typename std::enable_if<is_iterable<T>::value, size_t>::type SizeOf(const T &aValue)
{
size_t Result = sizeof(aValue);
for (T::const_iterator I = aValue.begin(); I != aValue.end(); ++I)
{
Result += SizeOf(*I);
}
return Result;
}
// Pair specialization.
template <typename T> typename std::enable_if<is_pair<T>::value, size_t>::type SizeOf(const T &aValue)
{
return SizeOf(aValue.first) + SizeOf(aValue.second);
}
// Array specialization.
template <typename T> typename std::enable_if<std::is_array<T>::value, size_t>::type SizeOf(const T &aValue)
{
size_t Result = sizeof(aValue);
for (T *I = std::begin(aValue); I != std::end(aValue); ++I)
{
SizeOf(*I);
}
return Result;
}
// Other types.
template <typename T> typename std::enable_if<std::is_pod<T>::value, size_t>::type SizeOf(const T &aValue)
{
return sizeof(aValue);
}
int main(int argc, char **argv)
{
int Int;
int *IntPtr = ∬
int twoints[2] = {0, 0};
int *twointpointers[2] = {IntPtr};
std::string SO("StackOverflow");
std::wstring WSO(L"StackOverflow");
std::map<std::string, char> m;
std::vector<float> vf;
m[SO] = 'a';
std::cout << "1: " << SizeOf(Int) << '\n';
// std::cout << "2: " << SizeOf(IntPtr) << '\n';
// std::cout << "3: " << SizeOf(twoints) << '\n';
// std::cout << "4: " << SizeOf(twointpointers) << '\n';
std::cout << "5: " << SizeOf(SO) << '\n';
std::cout << "6: " << SizeOf(WSO) << '\n';
std::cout << "7: " << SizeOf(m) << '\n';
std::cout << "8: " << SizeOf(vf) << '\n';
return 0;
}
The above code produces this output:
1: 4
5: 45
6: 58
7: 66
8: 20
If I uncomment the lines with the 2, 3 and 4 output the compiler shows the "ambiguous call" error. I really had thought that the output 2 will use the
is_pointer
speciallization and the output 3 and 4 will use theis_array
one. Well, I was wrong but I don't know why.I'm not fine with the way I get the total size of a container, i think that iterating all the items and calling the
SizeOf
for each item is a good choice but not for all the containers, instd::basic_string
doingsizeof(container) + sizeof(container::value_type) * container.size()
would be faster but I cannot realize how to specializate forbasic_string
.Talking about the detection classes (like the ones that detect iterable and pair), in some blogs articles and web examples about SFINAE I've seen that is a common practice to create a
true_type
andfalse_type
typedef
s, usually defined aschar
andchar[2]
; but I've found that some authors useschar
andlong
astrue_type
andfalse_type
. Anyone knows wich one is the best practice or the most standard one?.
Note that I'm not looking for answers like why you don't try "this library" or "this tool", my goal is practicing and understanding the SFINAE, any clue and advice is wellcome.