简体   繁体   中英

C++ base class constructor taking derived class as argument (?)

Use case:

  • Vector class (implementing some math) and a derived Vector2D class
  • Both classes should ideally allow "copy construction" from each other

Vector

namespace mu {
template<std::size_t N, typename T>
class Vector {
  public:
  // ...

  template <typename... TArgs>
  Vector(TArgs... args) : data({args...}) {}

  Vector(const Vector &other) = default; // copy constructor

  // ...
  protected:
    std::array<T, N> data;
};
}

Vector2D

namespace mu {
template<typename T>
class Vector2D : public Vector<2,T> {

  public:

  using Vector<2, T>::Vector; // inherit base class constructors

  Vector2D(const Vector<2, T>& other) : Vector<2, T>(other) {}

  // Vector2D specific functions, e.g. rotation
  //...

};
}

note: the actual classes contain a lot more but i condensed it down to the code that i think is most important here.

The problem is that i'm not able to implement a way such that a Vector can be constructed from a Vector2D , see code below. All other cases work fine.

// Example 1 (compiles)
mu::Vector<2, int> a{1, 2};
mu::Vector<2, int> b{a};

// Example 2 (compiles)
mu::Vector2D<int> c{1, 2};
mu::Vector2D<int> d{c};

// Example 3 (compiles)
mu::Vector<2, int> e{1, 2};
mu::Vector2D<int> f{e};

// Example 4 (doesn't compile)  <-- how to get this to work?
mu::Vector2D<int> g{1, 2};
mu::Vector<2, int> h{g};

Of course the more general question would be if inheritance is the right way to structure these classes. But i'd like Vector2D to have all the functionality of Vector and also additional functions that the Vector does not have.

Your Vector class has two constructor: a template one (intended for values) and the default copy constructor.

Problem: the copy constructor is preferred but only if there is an exact match.

So, initializing b with a

mu::Vector<2, int> a{1, 2};
mu::Vector<2, int> b{a};

the copy constructor is preferred because a is an exact match

But, initializing h with g

mu::Vector2D<int> g{1, 2};
mu::Vector<2, int> h{g};

g can be converted to a mu::Vector<2, int> but isn't an exact match, so the template constructor is preferred but the template constructor is incompatible.

A possible solution: SFINAE disable the template constructor when the there is only one argument and the argument is derived from mu::Vector .

For example

template <typename... TArgs,
          typename std::enable_if_t<sizeof...(TArgs) == N
                                or (not std::is_base_of_v<Vector, TArgs> && ...), int> = 0>
Vector(TArgs const & ... args) : data({args...}) {}

Leaving aside suitability of inheritance for this specific task, the immediate reason for the failure is the catch-all template <typename... TArgs> constructor. It will intercept every construction which is not a copy—and construction from a derived class object is not a copy. This is because conversion from Derived to Base is a conversion, and the template constructor doesn't require a conversion, so it's a better match.

You want to restrict the catch-all constructor to only be included in overload resolution if the arguments are suitable for constructing the std::array member. This is a standard application of SFINAE.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM