18

I have a class template which I use to get the size of a variable:

template <class T>
class Size
{
   unsigned int operator() (T) {return sizeof(T);}
};

This works fine but for strings I want to use strlen instead of sizeof:

template <>
class Size<char *>
{
   unsigned int operator() (char *str) {return strlen(str);}
};

The problem is when I create an instance of size with const char * it goes to the unspecialized version. I was wondering if there is a way to capture both the const and non-const versions of char * in on template specialization? Thanks.

Benjy Kessler
  • 7,356
  • 6
  • 41
  • 69

3 Answers3

11

Use this technique:

#include <type_traits>

template< typename T, typename = void >
class Size
{
  unsigned int operator() (T) {return sizeof(T);}
};

template< typename T >
class Size< T, typename std::enable_if<
                 std::is_same< T, char* >::value ||
                 std::is_same< T, const char* >::value
               >::type >
{
  unsigned int operator() ( T str ) { /* your code here */ }
};

EDIT: Example of how to define the methods outside of the class definition.

EDIT2: Added helper to avoid repeating the possibly long and complex condition.

EDIT3: Simplified helper.

#include <type_traits>
#include <iostream>

template< typename T >
struct my_condition
  : std::enable_if< std::is_same< T, char* >::value ||
                    std::is_same< T, const char* >::value >
{};

template< typename T, typename = void >
struct Size
{
  unsigned int operator() (T);
};

template< typename T >
struct Size< T, typename my_condition< T >::type >
{
  unsigned int operator() (T);
};

template< typename T, typename Dummy >
unsigned int Size< T, Dummy >::operator() (T)
{
  return 1;
}

template< typename T >
unsigned int Size< T, typename my_condition< T >::type >::operator() (T)
{
  return 2;
}

int main()
{
  std::cout << Size< int >()(0) << std::endl;
  std::cout << Size< char* >()(0) << std::endl;
  std::cout << Size< const char* >()(0) << std::endl;
}

which prints

1
2
2
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 1
    Thanks, I'm unfamiliar with the 'typename = void' syntax, how would I define operator() if I want to write the function body outside of the class and not inlined, in either case? – Benjy Kessler Feb 17 '13 at 22:12
  • 1
    It's just short for `typename Dummy = void` where `Dummy` is never used. I'll check and enhance the answer how to define `operator()` outside of the class... – Daniel Frey Feb 17 '13 at 22:14
  • Thanks and for the specialized case do I need to write that whole long thing in the function definition? – Benjy Kessler Feb 17 '13 at 22:16
  • Basically yes, you have to write it. See my second edit on how to use a helper to avoid repeating the condition manually. – Daniel Frey Feb 18 '13 at 00:25
6

And you should also be able to write, of course:

template <>
class Size<const char *>
{
   unsigned int operator() (const char *str) {return strlen(str);}
};

template <>
class Size<char *> : public Size<const char *>
{ };

...and, should you need to:

template <size_t size>
class Size<char[size]> : public Size<const char *>
{ };

template <size_t size>
class Size<const char[size]> : public Size<const char *>
{ };
vladr
  • 65,483
  • 18
  • 129
  • 130
  • This is the by far cleanest solution IMO. Reuse code without making it too verbose, for anyone else working with your code. – Smartskaft2 Mar 08 '21 at 14:16
4

One way is to use a helper function in order to determine if the template type is a char * or a char const *. You can do this with a simple struct. Then you can use SFINAE to select the proper specialization of your Size struct.

#include <cstring>
#include <iostream>

#include <boost/utility/enable_if.hpp>

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

template<>
struct is_cstring<char *> {
  enum { value = true };
};

template<>
struct is_cstring<char const *> {
  enum { value = true };
};

template<typename T, typename = void>
struct Size;

template<typename T>
struct Size<T, typename boost::disable_if<is_cstring<T> >::type> {
  unsigned int operator ()(T const &) const {
    return sizeof(T);
  }
};

template<typename T>
struct Size<T, typename boost::enable_if<is_cstring<T> >::type> {
  unsigned int operator ()(T const &str) const {
    return strlen(str);
  }
};

int main() {
  std::string blah = "afafasa";
  char *x = "asdfsadsad";

  std::cout << Size<int>()(4) << std::endl;
  std::cout << Size<char const *>()("blahblah") << std::endl;
  std::cout << Size<char *>()(x) << std::endl;
}

The printed result is:

4
8
10
Chris Hayden
  • 1,104
  • 6
  • 6