c++templatesoverloadingtemplate-specializationname-hiding

C++ Adding overloaded methods for specific template specializations


I have a pretty interesting problem: I have two template classes. One can take any template parameter, the other is more specialized (for this toy problem, we'll say it has to take floating points).

template< class T >
class CanBeAnything{ /*...*/ };

template< class T >
class MustBeFloat{ static_assert(is_floating_point<T>::value, ""); /*...*/ };

Now I have another template class Foo. Foo has no restrictions on it's template parameter, and a function foo that takes a CanBeAnything or a MustBeFloat of the same type. I'm hoping to use explicit template instantiation here, so I only want the MustBeFloat overload to exist when my template parameter is floating point.

The simplest solution seems to be to specialize Foo, but I don't like the idea of duplicating the interface across two classes. I came up with an almost working CRTP solution, with one problem that I'll mention in a minute

/* Traits object to get the value_type out of foo */
template<class FooType>
class FooTraits{};

/* Helper parent class with floating-point only methods */
template<class Derived, bool isFloatingPoint>
class FooSpecialization {}

template<class Derived>
class FooSpecialization<Derived, true>
{
   typedef typename FooTraits<Derived>::value_type value_type;
public:
   void foo( MustBeFloat<value_type> & x );
};

/* Front-end interface */
template<class T>
class Foo : public FooSpecialization< Foo<T>, is_floating_point<T>::value >
{
   typedef FooSpecialization< Foo<T>, is_floating_point<T>::value > Parent;
   typedef typename FooTraits< Foo<T> >::value_type value_type;
public:
   void foo( CanBeAnything<value_type> & x );
private:
   friend class Parent;
};

template<class T>
class FooTraits< Foo<T> >
   { public:  typedef T value_type; };

So here's the problem: As is, calls to foo( MustBeFloat<value_type> & ) are hidden in the child class by name hiding, and the compiler gives me "No matching call to method foo" error. If I add the line using Parent::foo; to bring it down, I get "foo does not exist in parent class" error when instantiating non-floating point Foo, since the method doesn't exist that far.

Any ideas? I'm okay with scraping this whole solution if a more elegant/working one is available.

EDIT: Just to clarify: I'm doing explicit instantiation here, which is why I need the method to only exist if I have a floating point template parameter.

template class Foo<int>;
template class Foo<float>;

This instantiates EVERY class member so methods that rely on not instantiating certain methods are a no go.

EDIT2: Okay, so I was over thinking this. Here's the solution I'm going with:

template<class T>
class Foo
{
public:
   template<class T2>
   void foo( MustBeFloat<T2> & x ){ static_assert( std::is_same<T,T2>::value, ""); /* ... */}
   void foo( CanBeAnything<T> & x ){ /* ... */ }
};

template class Foo<int>;
template class Foo<float>;
template void Foo::foo<float>(MustBeFloat<float> &);

And it all works. Yay! Thanks to people who helped lead me to this solution and came up with other, more inventive ones.


Solution

  • OK. So I haven't fully tested this but if you have replies then I'll either comment or modify this suggestion. But here is some sample code which should compile in a version of foo() in a simplified test case and have it specific to the type which the parent class uses:

    template< typename T >
    class TestClass
    {
      typedef struct PlaceholderType {};
    
    public:
      template< typename T2 >
      typename std::enable_if< !std::is_same<T2, PlaceholderType>::value && std::is_same<T, float>::value, void >::type MyFunc( T2 param ) { std::cout << "Float"; }
    
      template< typename T2 >
      typename std::enable_if< !std::is_same<T2, PlaceholderType>::value && !std::is_same<T, float>::value, void >::type MyFunc( T2 param ) { std::cout << "Non-float"; }
    };
    
    
    int main(int argc, char* argv[])
    {
      TestClass<int> intClass; // should only have the MyFunc(int) version available
      TestClass<float> floatClass; // should only have the MyFunc(float) version available
    
      intClass.MyFunc(5); // should output "Non-float"
      intClass.MyFunc(5.0f); // should output "Non-float"
      floatClass.MyFunc(2.0f); // should output "Float"
      floatClass.MyFunc(2); // should output "Float"
    }