-2

What is the best and most correct way to define a template function whose argument's type is required to be a subclass of a certain base class?

Here is what I have:

class A {};

class B : public A {};

class C {};

template <typename T>
int foo(T& x) // NB: need a solution that allows a return value
{
    A& dummy = x; // require T to be a subclass of A, else SFINAE
    return 1;
}

//int foo(C&) { return 2; }

int main()
{
   B b;
   C c;

   std::cout << foo(b) << "\n";
   std::cout << foo(c) << "\n"; // should fail unless foo(C&) is defined
}

Is the above correct? I don't like it because it doesn't express intent. I would prefer something that read more like the following bogus code:

template <class A T>
void foo(T& x) {}

Maybe enable_if can be used somehow?

(Background: In my production code the functions are operators. I need them to be templates for metaprogramming reasons (to deduce the compile time type of T). But I want to restrict them to only match subclasses of A.)

Update #1: In a related question on Template Constraints C++ The following is given:

static_assert(std::is_base_of<A, T>::value, "T must be a sublass of A");

This captures the intent better than A& dummy = x; however it suffers from the same problem that if void foo(C&) { } is commented out, foo<T> still gets specialized for the call to foo(c) and then the static_assert fails. Whereas I would like for the compiler to not even try to specialize foo<T> for cases where the parameter is not a subclass of A.

Update #2: Based on the accepted answer here: Why does enable_if_t in template arguments complains about redefinitions? the following code appears to be close, however it is C++14 specific. I would like a solution portable to current standards.

template <typename T, std::enable_if_t<std::is_base_of<A, T>::value>* = nullptr>
void foo(T& x)
{
}

The advantage of the above is that when void foo(C&) is defined, the compiler gives the correct error: "error: no matching function for call to 'foo(C&)'".

Community
  • 1
  • 1
Ross Bencina
  • 3,822
  • 1
  • 19
  • 33
  • 1
    Refer: http://stackoverflow.com/questions/122316/template-constraints-c – veda Nov 17 '15 at 06:53
  • You say you want to use SFINAE (where it wouldn't generate an error) if the wrong type is used, but then say that it _should_ generate an error? – Weak to Enuma Elish Nov 17 '15 at 06:55
  • There's no such thing as "*most* correct". – Karoly Horvath Nov 17 '15 at 06:56
  • If you want it to generate an error when you try to use something not derived from `A`, well... it does. – Weak to Enuma Elish Nov 17 '15 at 07:01
  • @JamesRoot I've updated the post to say "should fail to find a valid foo". Ideally the code should report that there is no matching foo for foo(c). It shouldn't specialize foo(T) and then fail on the assignment to dummy. – Ross Bencina Nov 17 '15 at 07:02
  • @KarolyHorvath if there is no correct solution, then of course there is most correct, it is the same as least wrong. – Ross Bencina Nov 17 '15 at 07:04
  • The "covariant" in the title sounds like this has someting to do with covariance. In that case its an X/Y question. What's this about covariance? – Cheers and hth. - Alf Nov 17 '15 at 07:09
  • @Cheersandhth.-Alf as far as I understand, and I could be mistaken, covariance is the standard term for this relationship in generic programming (Scala, Haskell, C#, Java). See e.g. https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx – Ross Bencina Nov 17 '15 at 07:16
  • Wow, downvotes? Could I have some feedback on why please? – Ross Bencina Nov 17 '15 at 07:17
  • Co-variance means that two types vary in the same direction (co), usually towards more specialized. E.g. a member function whose argument type is more specialized type in a derived class. C++ supports co-variance for pointer and reference function results (pure out-arguments). – Cheers and hth. - Alf Nov 17 '15 at 07:19
  • @Cheersandhth.-Alf yes. I want to require that `foo` matches a parameter of type `A` or any subclass of `A`. This seems to correlate to your definition of co-variance, but please correct me if I'm wrong. – Ross Bencina Nov 17 '15 at 07:22
  • @RossBencina I still don't understand. You don't want `foo` to be instantiated, but also want `foo` to not throw an error and have a valid return type? – Weak to Enuma Elish Nov 17 '15 at 07:25
  • @JamesRoot Obviously `foo` will fail if there is no valid match. The problem that I am trying to avoid is that with my example code, the compiler does try to specializes on `foo` and then fails with an error. In other words, the error message when `foo` is in scope indicates that the compiler tried to instantiate `foo` (i.e. we didn't get SFINAE after all). Same issue with gcc and clang. – Ross Bencina Nov 17 '15 at 07:35
  • @RossBencina What is your goal exactly when `foo` is passed a type that doesn't derive from `A`? – Weak to Enuma Elish Nov 17 '15 at 07:39
  • @JamesRoot If `foo` is passed a type that doesn't derive from `A` the compiler should behave as if my template definition of foo doesn't exist. – Ross Bencina Nov 17 '15 at 07:44
  • @RossBencina How is that going to be different than throwing an error with a `static_assert`? You would just get an undeclared identifier error. – Weak to Enuma Elish Nov 17 '15 at 07:47
  • @JamesRoot agree. the `static_assert` and `dummy` solutions are both incorrect. Just my best starting guesses. P.S. See "Update #2" – Ross Bencina Nov 17 '15 at 07:49
  • If you write an overload `int foo(C&)` (or even an specialization for `C`), it is normal that the generic template is not called (and so cannot `static_assert`)... – Jarod42 Nov 17 '15 at 10:11
  • @Jarod42: True. but if I don't write an overload for `int foo(C&)` I don't want to see any mention of `static_assert` either. The two answers using `enable_if` have the desired property. – Ross Bencina Nov 18 '15 at 01:01

2 Answers2

2

Depends on the check you actually want to do.

To check for a public, unambiguous base, use is_convertible on pointers:

template <class T>
auto foo(T& x) -> std::enable_if_t<std::is_convertible<T*, A*>{}/*, void*/> {
  // stuff
}

To check for any base whatsoever, public, protected or private, ambiguous or unambiguous, use is_base_of:

template <class T>
auto foo(T& x) -> std::enable_if_t<std::is_base_of<A, T>{}/*, void*/> {
  // stuff
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Is there a variant of this technique that can return non-void? Yes, the original example is abstracted, I need `foo()` to be able to return a type. – Ross Bencina Nov 17 '15 at 07:14
  • @RossBencina Sure. Just use something else as the second argument to `enable_if_t`. – T.C. Nov 17 '15 at 07:25
  • Thanks. There's also the way it's done at the end of the accepted answer here: http://stackoverflow.com/questions/31500426/why-does-enable-if-t-in-template-arguments-complains-about-redefinitions – Ross Bencina Nov 17 '15 at 07:28
0

The following is another way to do it, based on T.C.'s answer, and the accepted answer here: (Why does enable_if_t in template arguments complains about redefinitions?):

#include <type_traits>
#include <iostream>

// Provide enable_if_t in C++11
#if __cplusplus <= 201103L
namespace std {
template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
}
#endif

class A {};

class B : public A {};

class C {};

template <typename T, std::enable_if_t<std::is_convertible<T*, A*>::value>* = nullptr>
int foo(T& x)
{
  return 1;
}

//int foo(C& x) { return 2; }

int main()
{
  B b;
  C c;

  std::cout << foo(b) << "\n";
  std::cout << foo(c) << "\n"; // should fail unless foo(C&) is defined
}

This correctly ignores the templated foo when int foo(C& x) is undefined, and therefore gives the expected (desired) compiler error for the foo(c) line:

error: no matching function for call to 'foo'

Community
  • 1
  • 1
Ross Bencina
  • 3,822
  • 1
  • 19
  • 33