3

I am learning how to use std::enable_if and I have had some degree of success so far at conditionally enabling and disabling methods in my classes. I template the methods against a boolean, and the return type of such methods is an std::enable_if of such boolean. Minimal working example here:

#include <array>
#include <iostream>
#include <type_traits>

struct input {};
struct output {};

template <class io> struct is_input { static constexpr bool value = false; };

template <> struct is_input<input> { static constexpr bool value = true; };

template <class float_t, class io, size_t n> class Base {
private:
  std::array<float_t, n> x_{};

public:
  Base() = default;
  Base(std::array<float_t, n> x) : x_(std::move(x)) {}
  template <class... T> Base(T... list) : x_{static_cast<float_t>(list)...} {}

  // Disable the getter if it is an input class
  template <bool b = !is_input<io>::value>
  typename std::enable_if<b>::type get(std::array<float_t, n> &x) {
    x = x_;
    }

  // Disable the setter if it is an output class
  template <bool b = is_input<io>::value>
  typename std::enable_if<b>::type set(const std::array<float_t, n> &x) {
    x_ = x;
    }
};

int main() {
  Base<double, input, 5> A{1, 2, 3, 4, 5};
  Base<double, output, 3> B{3, 9, 27};

  std::array<double, 5> a{5, 6, 7, 8, 9};
  std::array<double, 3> b{1, 1, 1};

  // A.get(a);   Getter disabled for input class
  A.set(a);

  B.get(b);
  // B.set(b);   Setter disabled for input class

  return 0;
}

However, I cannot apply this procedure to conditionally enable constructors, since they have no return type. I have tried templating against the value of std::enable_if but the class fails to compile:

template <class io> class Base{
private:
  float_t x_;
public:
  template <class x = typename std::enable_if<std::is_equal<io,input>::value>::type> Base() : x_{5.55} {}
}

The compiler error looks like:

In instantiation of ‘struct Base<output>’ -- no type named ‘type’ in  ‘struct std::enable_if<false, void>’

As explained in this other post, when a class temnplate is instantiated, it instantiates all its member declarations (though not necessarily their definitions). The declaration of that constructor is ill-formed and hence the class cannot be instantiated.

How would you circumvent this issue? I Appreciate any help :)

EDIT:

I would like to have something like the following:

struct positive{};
struct negative{};

template <class float_t, class io> class Base{
private:
  float_t x_;
public:
  template <class T = io, typename = typename std::enable_if<
                          std::is_same<T, positive>::value>::type>
  Base(x) : x_(x) {}

  template <class T = io, typename = typename std::enable_if<
                          std::is_same<T, negative>::value>::type>
  Base(x) : x_(-x) {}

If that is possible at all.

enanone
  • 923
  • 11
  • 25

1 Answers1

5

There are 2 ways for constructors as you cannot use it in return type

Default template parameter:

template <class io> class Base
{
private:
    float_t x_;
public:
    template <class T = io,
              typename std::enable_if<std::is_equal<T, input>::value, int>::type = 0>
    Base() : x_{5.55} {}
};

Default argument:

template <class io> class Base{
private:
    float_t x_;
public:
    template <class T = io>
    Base(typename std::enable_if<std::is_equal<T, input>::value, int>::type = 0) :
        x_{5.55}
    {}
};

You cannot use SFINAE in your case as parameter depend only of your class parameter and not of template parameter of your function.

For:

template <class T = io, typename = typename std::enable_if<
                      std::is_same<T, positive>::value>::type>
Base(x) : x_(x) {}

template <class T = io, typename = typename std::enable_if<
                      std::is_same<T, negative>::value>::type>
Base(x) : x_(-x) {}

You do a common mistake: default template argument are not part of the signature, so you only declare and define twice

template <class, typename>
Base::Base(x);

That's why I used

typename std::enable_if<std::is_same<T, input>::value, int>::type = 0

where type is on left of =, so you will have 2 different type, so 2 different signatures.

C++20 adds requires which allow to discard method simply:

template <class float_t, class io> class Base{
private:
  float_t x_;
public:

  Base(x) requires(std::is_same<io, positive>::value) : x_(x) {}
  Base(x) requires(std::is_same<io, negative>::value) : x_(-x) {}

  // ...
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I understand the problem that you point out. If the constructor was not templated against a class parameter, but against it own parameter, we could use https://stackoverflow.com/questions/14603163/how-to-use-sfinae-for-selecting-constructors. But this is not the case here. Is there any way around? I have edited my question to make it more clear. Thank you for your answer! :) – enanone May 28 '18 at 07:58
  • 1
    Edited to pinpoint your other problem in your edit. – Jarod42 May 28 '18 at 08:07
  • That is some very valuable insight (for me) regarding default arguments and signature! But with the solution having `int`, the method is not disabled for `std::enable_if::type`. It stills compiles perfectly fine. I am thinking now that maybe `std::conditional` is what I am lloking for, right? – enanone May 28 '18 at 08:14
  • 1
    `std::enable_if::type` is an hard error, `std::enable_if::value,int>::type` with `cond::value` resulting to false would disallow the overload. `std::conditional` doesn't seem to be what you want. – Jarod42 May 28 '18 at 11:33
  • You are completly right! I was compiling a slightly different file than the one I posted here, where a different constructor was being called. And yeah, this is exactly what I needed. Thank you! – enanone May 28 '18 at 12:06
  • Did you perhaps meant to write `std::is_same` because there is no `std::is_equal`. – Stefan Fabian Sep 05 '19 at 10:39
  • Thanks that's what I was looking for. Would you mind elaborating why you used `T = io` instead of using `io` directly? – Stefan Fabian Sep 05 '19 at 10:45
  • SFINAE applies on template function and should be dependent on that template, if you use `io` directly, error comes from the class template parameter. C++20 adds a simple fix with `Base() requires(std::is_equal::value) : x_{5.55} {}`. – Jarod42 Sep 05 '19 at 10:51