0

I'm am quite new to C++ but I could not seem to find an answer to this question.

If I have a class template <Type> with a constructor taking in object type of ClassA<Type>, and I have another class derived in the following manner: class ClassB : public ClassA<Animal>, am I able to pass in an object of type classB into the first constructor?

c zl
  • 13
  • 3

1 Answers1

0

That depends entirely whether the constructor takes its argument by reference/pointer. If it does not, the object will be sliced into a ClassA<Type>.

#include <memory>

template <typename Type>
class ClassA {};

class ClassB : public ClassA<Animal> {};

template <typename Type>
class ClassUser1 {
public:
    explicit ClassUser1(ClassA<Type>);
}

template <typename Type>
class ClassUser2 {
public:
    explicit ClassUser2(std::unique_ptr<ClassA<Type>>);
}

In this setup, calling ClassUser1<Animal>::ClassUser1{ClassB{}} will construct a temporary ClassB, which will be used to move-construct a ClassA<Animal> (slicing the object).

Calling ClassUser2<Animal>::ClassUser2{std::make_unique<ClassB>()} will work as you would expect; the pointed-to instance remains a ClassB.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Thank you for your reply. This is first time I heard of slicing. I would assume if the constructor takes in the argument by reference, then I am able to pass in the derived class object into them. However, I get the following error when compiling/building: `note: candidate constructor not viable: no known conversion from 'std::list >' to 'list > &'`, which somehow indicates the derived classes cannot be implicitly converted by the compiler to the base class? – c zl Jun 03 '20 at 08:09
  • Just to clarify, notice in the example I posted here, I'm passing in a list of such class objects, instead of just a single object (The constructor expects a list of class objects). But I suppose the example still holds? – c zl Jun 03 '20 at 08:10
  • @czl Unfortunately, you can't do this because template arguments cannot be contravariant. In the example I gave, the `unique_ptr` is used to move-construct a `unique_ptr>`. Because these pointers are nested in a list, this isn't directly possible. You would need to copy the list, or you need to make the constructor a template that can take `std::list>` (or more simply `T`) and it could `static_assert` on the type of `T`, or simply proceed as though `T` is an acceptable type and fail to instantiate otherwise. – cdhowie Jun 03 '20 at 08:22
  • Template instantiations A and A are distinct and unrelated types, even if the underlying arguments X and Y are convertible to each other. Thus, a list of X cannot be converted to a list of Y because the two types, though "lists", are uniquely unrelated types. One must explicitly write code to permit conversions between them, like for any types. (For example, a templated operator=, constructor, or other function.) Stdlib classes (like list) cannot be changed like that, however. – Chris Uzdavinis Jun 03 '20 at 12:11
  • @czl One fairly straightforward option would be to have the constructor be a template that takes a pair of iterators (`template ClassName(Iterator begin, Iterator end);`) instead of a list. Then you could use this constructor with other container types, and you _can_ have the `shared_ptr`s be contravariant. – cdhowie Jun 03 '20 at 13:13
  • Thanks guys! I totally realised a list of type ClassB cannot be treated as a list of type ClassA even if ClassB is derived from ClassA. What I probably can do is either to a single list transformation, or just initialise a list of ClassA right from the beginning and insert ClassB objects directly into them (please correct them if I am wrong). – c zl Jun 04 '20 at 01:31