1

I have several functions that I want to specialize based on type qualities, such as "character, signed-integer, unsigned-integer, floating-point, pointer"; using type_traits seems like the way to do this, and have code similar to the to the following:

#include <tr1/type_traits>
#include <iostream>

template<bool, typename _Tp = void>
struct enable_if 
{ };

template<typename _Tp>
struct enable_if<true, _Tp>
{
    typedef _Tp type;
};


template< typename T >
inline void
foo_impl( typename enable_if< std::tr1::is_integral< T >::value, T >::type const& )
{
    std::cout << "This is the function-overloaded integral implementation.\n";
}

template< typename T >
inline void
foo_impl( typename enable_if< std::tr1::is_floating_point< T >::value, T >::type const& )
{
    std::cout << "This is the function-overloaded floating-point implementation.\n";
}

template< typename T >
inline void
function_overloads_foo( T const& arg )
{
    foo_impl< T >( arg ); // vital to specify the template-type
}

void function_overloads_example()
{
    function_overloads_foo( int() );
    function_overloads_foo( float() );
}

except in my real code, I also have bar,baz, etc., along with foo.

However, I would like to group all of these functions per quality into one templated class as static methods. How is this best done? Here's my naive, and broken attempt to use Tags, SFINAE, and partial-specialization:

struct IntegralTypeTag;
struct FloatingPointTypeTag;

template< typename T, typename U = void >
class Foo
{
};

template< typename T >
class Foo< T, typename enable_if< std::tr1::is_integral< T >::value, IntegralTypeTag >::type >
{
    static void foo( T const& )
    {
        std::cout << "This is the integral partial-specialization class implementation.\n";
    }
};

template< typename T >
class Foo< T, typename enable_if< std::tr1::is_floating_point< T >::value, FloatingPointTypeTag >::type >
{
    static void foo( T const& )
    {
        std::cout << "This is the floating-point partial-specialization class implementation.\n";
    }
};

template< typename T >
inline void
partial_specialization_class_foo( T const& arg )
{
    Foo< T >::foo( arg );
}

void partial_specialization_class_example()
{
    partial_specialization_class_foo( int() );
    partial_specialization_class_foo( float() );
}

Note: in my real code, I'd have bar,baz, etc., along with foo static-methods.

FYI, this is C++03.

As an aside, am I doing the templated function overloading in the conventional way?

Nawaz
  • 353,942
  • 115
  • 666
  • 851
Charles L Wilcox
  • 1,126
  • 8
  • 18
  • I don't understand why `function_overload_foo()` is "vital to specify the template-type". You're not doing anything there, just forwarding – Andy Prowl Feb 27 '13 at 14:55
  • Yes, I thought so too, but my compiler 'gcc-4.7' complains without it: error: no matching function for call to ‘foo_impl(const int&)’ note: candidates are: note: template void foo_impl(const typename enable_if::value, T>::type&) note: template argument deduction/substitution failed: note: couldn't deduce template parameter ‘T’ note: template void foo_impl(const typename enable_if::value, T>::type&) note: template argument deduction/substitution failed: note: couldn't deduce template parameter ‘T’ – Charles L Wilcox Feb 27 '13 at 15:43
  • 1
    @AndyProwl: If you don't mention the template argument, the compiler cannot deduce it from the function argument, because it is non-deducible context. [See this to know why it is non-deducible](http://stackoverflow.com/questions/6060824/c-template-argument-can-not-be-deduced) – Nawaz Feb 28 '13 at 06:28
  • @Nawaz: Of course. I just had read the whole thing too superficially. Thank you. – Andy Prowl Feb 28 '13 at 13:51

3 Answers3

2

Here is one approach:

#include <tr1/type_traits>
#include <iostream>

struct IntegralTypeTag;
struct FloatingPointTypeTag;

template <
  typename T,
  bool is_integral = std::tr1::is_integral<T>::value,
  bool is_floating_point = std::tr1::is_floating_point<T>::value
> struct TypeTag;

template <typename T>
struct TypeTag<T,true,false> {
  typedef IntegralTypeTag Type;
};

template <typename T>
struct TypeTag<T,false,true> {
  typedef FloatingPointTypeTag Type;
};

template <typename T,typename TypeTag = typename TypeTag<T>::Type> struct Foo;


template <typename T>
struct Foo<T,IntegralTypeTag> {
  static void foo( T const& )
  {
    std::cout << "This is the integral partial-specialization class implementation.\n";
  }
};

template <typename T>
struct Foo<T,FloatingPointTypeTag> {
  static void foo( T const& )
  {
    std::cout << "This is the floating-point partial-specialization class implementation.\n";
  }
};

template< typename T >
inline void
partial_specialization_class_foo( T const& arg )
{
      Foo< T >::foo( arg );
}

int main(int,char**)
{
  partial_specialization_class_foo(int());
  partial_specialization_class_foo(float());
  return 0;
}
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Your answer works, uses Tags like I was trying, and sequesters all the type_traits checking to the one `TypeTag`. However, the whole structure seems more complicated than necessary to me, so after reading the answers here, I was able to refine my original approach to not rely on Tags at all. I posted the code and explanation as a separate answer. – Charles L Wilcox Feb 27 '13 at 16:34
2

After observing Vaughn's correct answer, I wanted to simplify it more. I was able to remove the use of Tags and extra trait-classes to come up with the following structure:

template< typename T, typename U = T >
struct Foo
{
};

template< typename T >
struct Foo< T, typename enable_if< std::tr1::is_integral< T >::value, T >::type >
{
    static void foo( T const& )
    {
        std::cout << "This is the integral partial-specialization class implementation.\n";
    }
};

template< typename T >
struct Foo< T, typename enable_if< std::tr1::is_floating_point< T >::value, T >::type >
{
    static void foo( T const& )
    {
        std::cout << "This is the floating-point partial-specialization class implementation.\n";
    }
};

template< typename T >
inline void
partial_specialization_class_foo( T const& arg )
{
    Foo< T >::foo( arg );
}

void partial_specialization_class_example()
{
    partial_specialization_class_foo( int() );
    partial_specialization_class_foo( float() );
}

I think this works by providing two template-type parameters to the class, where the second one is compile-time conditional:

  • This defaults to being same as the first parameter, so the caller only focuses on the first parameter.
  • Only partial-specializations that have the same second template param as the first can be matched.
  • When enable_if fails, the whole partial-specialization is unavailable to match against.

I find it less cumbersome to understand, comparatively.

Charles L Wilcox
  • 1,126
  • 8
  • 18
  • Turns out, I was able to refine my answer even more, after reading [Question 7776448](http://stackoverflow.com/questions/7776448/sfinae-tried-with-bool-gives-compiler-error-template-argument-tvalue-invol) I took it a step further by using std::integral_constant< typename bool, bool b >, and those that are directly available via type_traits' "::type" member. – Charles L Wilcox Feb 27 '13 at 22:06
0

IntegralTypeTag in the enable_if will get in the way. The default for the second parameter of Foo is void, which is not the same as IntegralTypeTag, so the specialization of Foo will fail to match.

Ie, Foo< int, void > (which is what you get when you do Foo<int>) does not match Foo< int, IntegralTypeTag >, which your intended int specialization is (after the enable_if logic).

Tagging is the result of a type_traits class, which you can then use to simplify other type_traits classes.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524