0

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.

Hounddog
  • 385
  • 1
  • 10
  • Just to clarify: when value_type is a float, you want Foo to have both MustBeFloat and CanBeAnything versions of foo() ? However if value_type is not a float then it should only have the CanBeAnything version of foo() ? – qeadz May 01 '14 at 17:43
  • @qeadz That is the intent, yes. Sorry if it wasn't clear from the description. – Hounddog May 01 '14 at 17:51
  • 1
    OK. Well examples using "foo" and such are always dumbed down so maybe this suggestion won't work. If all you are needing is for a specialized version of foo() to be present when value_type is a float then how about not deriving from FooSpecialization at all. Just have multiple versions of the function foo() inside class Foo. Use enable_if to only have the versions compiled in that you want based on value_type. I can type this up in an answer with a code snippet if it'll be clearer. – qeadz May 01 '14 at 17:54
  • ...Honestly this is the first I've ever heard of using "enable_if". I did a mockup and this is EXACTLY what I need. If you want to copy-paste that as the actual answer, I'll mark it as correct. – Hounddog May 01 '14 at 18:05
  • Your issue is resolved so Im now less inclined to spend time going through the motions. If someone posts a more detailed answer then do mark it as correct - good quality replies take time and are good for this site in general (I'm just too lazy to do them all the time). – qeadz May 01 '14 at 18:14
  • Actually, I just went to implement it, and it doesn't work for me, at least not how I'm using it--perhaps there's a better way to use it than I was doing. My thought was to do something along the lines of "void foo(MustBeFloat & x, typename std::enable_if< std::is_floating_point::value >::type * y = 0);" Unfortunately with explicit instantiation, this causes a compile error when initializing Foo. Is there a better way to do this than what I was attempting? – Hounddog May 01 '14 at 18:17
  • The downsides of quick comments :) I assumed the function would be a template so that it can use the enable_if. You'd then only enable_if when the type is float. The other foo() function may have to be a template too and enable_if for the case when the type is not a float. – qeadz May 01 '14 at 18:20
  • I'll work on an example to post in a bit, however I have a meeting shortly so be patient - it could be a couple of hours. – qeadz May 01 '14 at 18:29
  • That's fine, take your time obviously. I mucked around with it for a bit, and it seems to me at least, no matter how I template it, enable_if isn't going to work for me, because the intent is to throw compile errors if I instantiate a function that is improper. In my case, I'm instantiating explicitly "template class Foo" and "template class Foo", which attempts to instantiate all their members, and is throwing me a curveball when attempting to instantiate foo( MustBeFloat ). – Hounddog May 01 '14 at 18:39
  • Your first attempt would work if you just move `foo(CanBeAnything&)` into `FooSpecialization`. – Oktalist May 01 '14 at 18:47
  • OK. I wrote a quick code snippet. I'm due in the meeting now, so it's really dumbed down but hopefully can be extrapolated to your more complex situation. – qeadz May 01 '14 at 18:48
  • @Oktalist But then I would have to define the interface twice, once for the base case and one for the specialized case. While not the worst thing in the world, I was hoping for a solution that didn't involve that. – Hounddog May 01 '14 at 18:49
  • Or you could add another level of inheritance and move `foo(CanBeAnything&)` into `FooSpecializationBase`. Six of one, half a dozen of the other. – Oktalist May 01 '14 at 18:51
  • That's another good way of doing it. Thanks for the idea. – Hounddog May 01 '14 at 19:01

2 Answers2

1

This might suffice:

template <class T, bool isFloat, class Other>
struct FooTraits;
template <class T, class Other>
struct FooTraits<T, true, Other> { typedef MustBeFloat<T> MaybeFloat; };
template <class T, class Other>
struct FooTraits<T, false, Other> { typedef Other MaybeFloat; };

template <class T>
class Foo
{
    template <class U> friend class FooTraits<U>;
    class PrivateType {};
public:
    typedef typename FooTraits<T,
                               std::is_floating_point<T>::value,
                               PrivateType>::MaybeFloat MaybeFloat;
    void foo(CanBeAnything<T>&);
    void foo(MaybeFloat&);
};

If T is floating point then MaybeFloat will be a typedef to MustBeFloat<T>. Otherwise, it will be a private member class of Foo so it would be impossible for a caller of foo() to synthesize an lvalue of that type.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
  • This is really clever. I'm going to look at the enable_if solution, but this is an interesting way of avoiding the problem. And it works, which is helpful too. – Hounddog May 01 '14 at 18:51
1

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"
}
qeadz
  • 1,476
  • 1
  • 9
  • 17
  • Your magics work. I'm going to select this as the correct answer since you suggested it first, but anyone reading this in the future should look at the other solutions suggested by Oktalist because they also solve the problem. – Hounddog May 01 '14 at 19:00
  • Just one second. I have improved it (the pizza is still arriving for our lunch meeting so I had time). This version I mention is still technically not right. I'm replacing the answer with the updated version now. – qeadz May 01 '14 at 19:04
  • This is kind of solving a different problem than I'm doing...I don't really want an non-float version available at all. But you did give me the idea for the solution I'm using...simply template the function so that it doesn't get instantiated with the rest of the class. No enable_if necessary. – Hounddog May 01 '14 at 19:29