Question

I have a class, let's call it Foo with several methods:

template<typename T>
class Foo {
public:
   Foo()               { /* ... */ }
   bool do_something() { /* ... */ }

   // This method should be callable only if:
   // std::is_floating_point<T>::value == true
   void bar() { 
      // Do stuff that is impossible with integer
   }
};

I would like to be able to construct both Foo<double> and Foo<int> But I don't want to allow calls to bar() when the type T is not a floating point type. I also want the error to be generated at compile-time and not at the run-time. So, what I want is:

Foo<double> a;
a.bar();                        // OK
Foo<int> b;
bool res = b.do_something();    // OK
b.bar();                        // WRONG: compile error

I tried a lot of things with enable_if (with posts like this or this one) but I can't use anymore the int type with Foo. For example:

typename std::enable_if<std::is_floating_point<T>::value>::type
bar() { /* ... */ } 

main.cpp:112:28:   required from here
foo.h:336:5: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
 bar() {

How can I constrain the use of bar() to floating point types but allow integer type to use in other places?

Was it helpful?

Solution

void bar() { 
   static_assert(std::is_floating_point<T>::value,
                 "this method can only be called for floating-point Foos!");
   // do stuff
}

OTHER TIPS

The basic solution is to use a static_assert within the body of bar. This generates an error if and only if someone tries to call it. It does not, however, block people from finding it, and the error is "late" in that it doesn't enable SFINAE detection of the existence of bar.

As an alternative to static_assert, you can use SFINAE to conditionally allow bar to participate in overload resolution, and possibly block it from being found to be called at all. You have to be careful, because the SFINAE must theoretically have a specialization that compiles (otherwise your program is ill-formed with no diagnosis required), and it only works on a template method.

private:
  struct blocked {};
public:
  template<typename Unused=void, 
           typename=typename std::enable_if< 
                                     std::is_same<Unused, blocked>::value ||                            
                                     std::is_floating_point<T>::value 
                                           >::type>
  void bar() {
    // code
  }

This blocks the method bar from being considered during overload resolution, rather than making selecting it an error. This is a subtle difference, but matters a lot if bar is overridden.

which is pretty obtuse. In C++1y with concepts lite, requires clauses are supposed to do the right thing within a template without the above obtuse stuff.

In practice, the blocked type is probably not needed, as I am not aware of a compiler that actually enforces the ill-formedness of a template that has no valid specializations. That clause is probably there to allow future revisions to do more checking prior to instantiation.

The final method I can think of involves a CRTP base class that specializes based on the trait you want to create/destroy bar based on.

template<typename D, bool test>
struct maybe_bar {};
template<typename D>
struct maybe_bar<D, true> {
  D* self() {
    static_assert( std::is_base_of< maybe_bar, D >::value, "CRTP failure" );
    return static_cast<D*>(this);
  }
  D const* self() const{
    static_assert( std::is_base_of< maybe_bar, D >::value, "CRTP failure" );
    return static_cast<D const*>(this);
  }
  void bar() {
    // use self() in here to get at your this pointer instead of this
  }
};

then at the point of use:

template<typename T>
struct my_type:maybe_bar<T, std::is_floating_point<T>::value> {
};

and bar is conditionally created or not.

This is less obtuse, but more verbose, and it places the implementation seriously out-of-line with the rest of the class body.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top