2

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;
    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
  1. 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 the is_array one. Well, I was wrong but I don't know why.

  2. 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, in std::basic_string doing sizeof(container) + sizeof(container::value_type) * container.size() would be faster but I cannot realize how to specializate for basic_string.

  3. 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 and false_type typedefs, usually defined as char and char[2]; but I've found that some authors uses char and long as true_type and false_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.

PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
  • 1). because array name — its a pointer to its first cell. They are recognized as pointers. Hence you have ambiguous call. I'd recommend you to use another version of overloaded `SizeOf` for pointers. Calculating array size is better with `sizeof( _array ) / sizeof( _array[0] )`. 2). In this case should be used template-parameters for template. Like: `template class Container> std::enable_if<...>::type SizeOf( const T& _value )` where inside you can declare a variable `Container`. Which will be specialized for std::basic_string. – Pie_Jesu Sep 20 '12 at 06:58
  • 3). No difference, since long and char have different sizes (I hope in all compilers). – Pie_Jesu Sep 20 '12 at 06:59
  • 1
    @Pie_Jesu: No, an array name is not a pointer to its first element. It can _convert_ to one. This is critically important in overload resolution, which is decided based on the best _conversion_ sequence. – MSalters Sep 20 '12 at 08:28
  • @MSalters are right, recently [R. Martinho Fernandes](http://stackoverflow.com/users/46642/r-martinho-fernandes) advice me to read [this answer](http://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c/4810668) to understand the `array[]` vs `pointer` relationship.. – PaperBirdMaster Sep 20 '12 at 08:36

3 Answers3

2

1.You should read about POD concept in C++11. Array of POD-type elements or pointer to POD-type element are POD-types http://en.cppreference.com/w/cpp/concept/PODType

For example following code will compile well http://liveworkspace.org/code/81627f5acb546c1fb73a69c45f7cf8ec

2.Something like this can help you

template<typename T>
struct is_string
{
   enum
   {
      value = false
   };
};

template<typename Char, typename Traits, typename Alloc>
struct is_string<std::basic_string<Char, Traits, Alloc>>
{
   enum
   {
      value = true
   };
};

Functions

// Iterable class specialization.
template <typename T> typename std::enable_if<is_iterable<T>::value && !is_string<T>::value, size_t>::type SizeOf(const T &aValue)
{
    size_t Result = sizeof(aValue);

    for (typename T::const_iterator I = aValue.begin(); I != aValue.end(); ++I)
    {
        Result += SizeOf(*I);
    }

    return Result;
}

template <typename T> typename std::enable_if<is_string<T>::value, size_t>::type SizeOf(const T& aValue)
{
   return sizeof(aValue) + sizeof(typename T::value_type) * aValue.length();
}

3.No information in standard, that sizeof(long) should never be equal to sizeof(char), but sizeof(char) cannot be equal to sizeof(char[2]), so, second variant is preferable i think.

ForEveR
  • 55,233
  • 2
  • 119
  • 133
2

Regarding question #3, I think that in C++11, it's much cleaner (and more clear) to use decltype instead of sizeof to obtain an integral constant such as std::true_type and std::false_type.

For example, your is_iterable:

#include <type_traits> // std::true_type, std::false_type

// Iterable class detector
template <typename T> class is_iterable {
  template <typename U> static std::true_type test(typename U::const_iterator *);
  template <typename U> static std::false_type test(...);

public:
   // Using decltype in separate typedef because of gcc 4.6 bug:
   // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=6709
   typedef decltype(test<T>(0)) result_type;
   static const bool value = result_type::value;
};
Bruno De Fraine
  • 45,466
  • 8
  • 54
  • 65
1
  1. Your "specializations" for pointers etc. are in fact not specializations. They're overloads.

  2. The compiler first performs overload resolution, and only then checks for specializations. There's formally no such thing as an "ambiguous specialization". Your cases 2,3 and 4 fail already in overload resolution, precisely because you have no specializations.

  3. Overload resolution is decided on argument types only. Your overloads only differ in return type. Certainly some overloads may be disabled, but you would need to disable all overloads but one. Currently, a POD array enables both your POD and array overloads.

  4. For a container, the better solution is probably to use Container.size().

  5. char[2] is preferred because sizeof(long) can be 1, according to the standard.

One question not asked, which I'll answer anyway, is "how should I write the array overload then"? The trick there is a reference to an array:

template<typename T, unsigned N> 
constexpr size_t SizeOf(const T (&aValue)[N])
{
  // return N * sizeof(T); If you want to do the work yourself
  return sizeof(aValue); // But why bother?
}
MSalters
  • 173,980
  • 10
  • 155
  • 350