6

I have codes like this:

class Bar {
 public:
  void print() {
    std::cout << "bar\n";
  }
};

template<typename T>
class Foo {
 public:
  template <typename std::enable_if<std::is_base_of<T,Bar>::value,T>::type>
  void print() {
    t.print();
  }

 template <typename>
  void print() {
    std::cout << t << std::endl;
  }
 private:
  T t;
};

int main() {
//  Foo<int> foo1;
  Foo<Bar> foo2;
  foo2.print();
}

The purpose of this code is that: If the T t is a Bar or a subclass of Bar, then foo.print() is deduced to void print() {t.print();}, otherwise deduced to void print() {std::cout << t << std::endl;}, but things didn't work as I expect. The compiler errors:

"a non-type template parameter cannot have type 'typename std::enable_if::value, Bar>::type' (aka 'Bar')",

What's wrong with this code?

iammilind
  • 68,093
  • 33
  • 169
  • 336

3 Answers3

6
  1. You should make both the overloading of print() to function template (to make SFINAE working), otherwise the non-template function is always preferred.

  2. You should let print() taking its own template type parameter; type cheking shouldn't be performed on the class template parameter T directly, function templates overload resolution and SFINAE are performed on the function templates themselves, the class template doesn't involve in.

  3. You can move the part of std::enable_if to the return type.

  4. You should change the order specified to std::is_base_of (i.e. std::is_base_of<Bar, X>, not std::is_base_of<X, Bar>) if you want the type to be Bar or the derived class of Bar.

e.g.

template <typename X = T>
typename std::enable_if<std::is_base_of<Bar, X>::value>::type print() {
  t.print();
}

template <typename X = T>
typename std::enable_if<!std::is_base_of<Bar, X>::value>::type print() {
  std::cout << t << std::endl;
}

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • what if I want keep my function returning void? Or in other cases the return type is fixed? –  Jul 19 '19 at 03:30
  • 1
    @reavenisadesk If you don't specify, the return type is `void` (as my sample code). If you want other types, you can specify explicitly to `std::enable_if`, anyway, the type is fixed. – songyuanyao Jul 19 '19 at 03:35
  • Ahh, you came to the same conclusion I did. I see now. I was initially confused because you made it look as if they were standalone functions. – Omnifarious Jul 19 '19 at 03:43
  • Hi, another question, in your example, if I change `X` to `T` in `std::is_base_of`, the code will not compile, what's the difference? Since `typename X = T`? –  Jul 19 '19 at 03:46
  • 1
    @reavenisadesk `std::enable_if` should depend on the template parameter of the function template `print()`'s own; not the one of the class template `Foo`. – songyuanyao Jul 19 '19 at 03:49
  • Does that mean template don't "share" template parameters? –  Jul 19 '19 at 03:53
  • 1
    @reavenisadesk if `enable_if` doesn't depend on a template parameter of the `print` function, the compiler expands it immediately and that will result in an error in at least one case. Using the template parameter of the `print` function forces the compiler to wait to expand the return type until it tries to expand the whole `print` template. And since substitution failure is not an error, and there is a version that expanded correctly with no errors, the compiler doesn't flag it as an error. – Omnifarious Jul 19 '19 at 03:53
  • 1
    @reavenisadesk Function templates overload resolution and SFINAE are performed on the function templates themselves; the class template doesn't involve in. – songyuanyao Jul 19 '19 at 03:55
  • @Omnifarious when you mention "expands it immiediately", does that mean the SNINAE is not gonna happen and there's no deduce at all? –  Jul 19 '19 at 03:57
  • @reavenisadesk - There's no deduction at the time the function is called. – Omnifarious Jul 19 '19 at 15:52
1

Since you are actually interested in wether a type has the member function print or has defined the operator<<, you should also constrain it this way.

With the upcoming C++20 standard, we are getting concepts & constraints. With that in mind, we can do the following:

namespace traits
{
template<typename T>
concept has_print_v = requires(T&& t) { t.print(); };

template<typename T>
concept has_ostream_op_v = requires(T&& t, std::ostream& os) { os << t; };
} // end of namespace traits

And use the concepts like this:

template<typename T>
class Foo
{
public:
    void print()
    {
        if constexpr (traits::has_print_v<T>) { t.print(); }
        else if constexpr (traits::has_ostream_op_v<T>) { std::cout << t << "\n"; }
    }
private:
    T t;  
};

LIVE DEMO

serkan.tuerker
  • 1,681
  • 10
  • 20
0

You should be adding template parameter while calling print() as it's a template method itself. In any case, your design is overly complicated!

It becomes very simple with C++17, where you need only one Foo<T>::print() method.

void print() {
  if constexpr(std::is_base_of_v<T,Bar>) // ignores `Foo<int>`
    t.print();
}

Demo.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • does `if constexpr(std::is_base_of_v)` really generate a compare command?(asked in efficiency aspect) –  Jul 19 '19 at 04:04
  • @reavenisadesk, it's very efficient, because `if` condition is evaluated during the compile time. This is as good as what you do using `enable_if` kind of type_traits, but very concise. In nutshell, `if constexpr` is a notebook example for your kind of query. :-) – iammilind Jul 19 '19 at 06:41