0

I would like to make a template class limit an overloaded method like this example:

template<int N>
class foo {
  public:
    void add(int a, int* b) {*b = a + N;} 
    int add(std::enable_if<N == 0, int> a, int b) {return a + b}
}

(Example changed to reflect complications not present in the original example.)

However, I am using Visual Studio, whose support for SFINAE (and thus enable_if) is still not very good. (I'm not sure if it would work in that simple example; in my actual situation I'm using std::enable_if on a class reference and using a member of that class, and it doesn't work.) The answer to this (which has essentially the same error message I got) suggested using tag dispatching as an alternative, but I'm not sure how that would work if I want the function to simply be unavailable at all with those argument types, or if I want to overload a constructor in this manner.

  • Should be `int add(std::enable_if::type a, int b)` in C++11, or `int add(std::enable_if_t a, int b)` in C++14 – Justin Oct 26 '17 at 21:26
  • Thank you; I'll have to try that out. If I was simply misusing enable_if and fixing it solves that, that would be a convenient solution. – Yitzhak Kornbluth Oct 26 '17 at 22:06
  • 1
    The problem isn’t Visual Studio; it’s that this isn’t valid. The parameter to `enable_if` must depend on a template parameter *of the function*, not *of the class*. Here, by the time you try to instantiate `add`, the compiler already knows what `N` is, so the `enable_if` is invalid. – Daniel H Oct 26 '17 at 22:25
  • Daniel H: Thank you. I'd been misunderstanding what the problem was; looking with the information you provided led me to https://stackoverflow.com/questions/17842478/select-class-constructor-using-enable-if. – Yitzhak Kornbluth Oct 27 '17 at 14:36

3 Answers3

3

It needs to be made into a function template. And the condition in the enable_if needs to be made dependent on a template parameter of the function template so that it is not substituted into early.

template<int M = N, 
         class = typename std::enable_if<M == 0>::type>
int add(int a, int b) { return a + b; }
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • You might want to make that `template ::type>`, or you could just call `add<0>` for any instantiation. – Daniel H Oct 26 '17 at 22:23
  • @DanielH Murphy, Machiavelli, etc. – T.C. Oct 26 '17 at 22:35
  • I had actually not heard that metaphor before. I agree with it to a point, but if I see a template method in a class and I’m not too familiar with SFINAE or template metaprogramming (which is the case for a lot of C++ programmers), I won’t know which template parameters it’s safe to use myself and which it’s not. In this case it’s simple, but where I first saw the trick of putting the extra `typename...` pack, there were some template parameters which were actually parameters for the caller, and it was useful to distinguish them. – Daniel H Oct 27 '17 at 15:13
0

Usually I see std::enable_if used with return type

template<int N>
class foo {
  public:
    int add(int a) 
     {return a + N;} 

    template <int M = N>
    typename std::enable_if<M == 0, int>::type add(int a, int b)
     {return a + b}
}

This didn't work?

Another way can be the use of a base class (bar, in the following example) that define only add(int, int) and only in the required specialization.

template <int N>
struct bar
 { int add (int a, int b) = delete; };

template <>
struct bar<0>
 { int add (int a, int b) { return a + b; } };

template <int N>
struct foo : public bar<N>
 { using bar<N>::add; void add (int a, int * b) { *b = a + N; } };

int main()
 {
   foo<0>  f0;
   foo<1>  f1;

   int b { 0 };

   f0.add(1, &b);
   f0.add(1, 2);

   f1.add(1, &b);
   //f1.add(1, 2); // compilation error
 }

Observe that the using bar<N>::add in foo is required because, otherwise, the add(int) in foo hide the add(int, int) in bar.

And that this using impose the int add (int a, int b) = delete; in the generic version of bar<N>.

If the name of the two function is different, say add2() for the version with 2 integer arguments, the example become simply as follows

template <int N>
struct bar
 { };

template <>
struct bar<0>
 { int add2 (int a, int b) { return a + b; } };

template <int N>
struct foo : public bar<N>
 { void add (int a, int * b) { *b = a + N; } };

int main()
 {
   foo<0>  f0;
   foo<1>  f1;

   int b { 0 };

   f0.add(1, &b);
   f0.add2(1, 2);

   f1.add(1, &b);
   //f1.add2(1, 2); // compilation error
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thanks. Return type is normal, but as mentioned I'm using it in a constructor, where there is no return type. The other idea might work; is there a reason it requires a base class rather than just defining it with those arguments directly as delete, and then specializing? – Yitzhak Kornbluth Oct 26 '17 at 22:12
  • @YitzhakKornbluth - yes: with a constructor you can't enable/disable the return value; see also the T.C. answer: should work with constructors too. The reason for a base class is that in a base class you can differentiate the only function that is different in the two cases (the `add()` returning `int`); if you specialize the entire `foo` class/struct (I suppose a more complicated class/struct) you have to replicate all common code in both specializations. – max66 Oct 26 '17 at 22:17
  • @YitzhakKornbluth - answer modified to reflect your modified example (and corrected an error in the return type enable/disable example). – max66 Oct 26 '17 at 22:21
0

No simple transformation of your code is legal. The signature of methods to template classes must be legal.

template<class D, bool> // false case
struct add_base {
  void add(int* a, int b) {*a += b}      
  D*self(){return static_cast<D*>(this);}
  D const*self()const{return static_cast<D const*>(this);}
};
template<class D>
struct add_base<D,true>:add_base<D,false> {
  using base=add_base<D,false>;
  using base::self;
  using base::add; // for overload resolution
  int add(int a, int b) {return a + b}
};

template<int N>
class foo: add_base<foo<N>, N==0> {
  using base = add_base<foo<N>, N==0>;
  template<class D, bool b>
  friend class add_base<D, b>;// may be a syntax error here.
public:
  using base::add;
};

here I assume the real add wants to use this; in this case use self() in its implementation.

There are other techniques using template parameters with default values, but their legality under a strict reading of the standard is questionable. The problem is the standard mandates all template functions have a valid specialization, and mostnuse of those techniques breaks that rule. There are different readings of the standard under which they may be legal.

You could also wait for concepts.

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