C++ SFINAE 构造函数也接受派生 class

C++ SFINAE constructor accepting derived class too

入门:

我有一个Vector和一个Vector2Dclass。前者应该包含一个允许逐元素类型转换的构造函数。派生的 class 也应该被允许。在某些情况下,它已经有效(请参见下面的示例),但我认为缺少一些 SFINAE 魔法。

Vector

#include <array>
#include <type_traits>

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

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

    // this should always be called for type casting:
    template <std::size_t Nn, typename Tt>
    Vector(const Vector<Nn, Tt> &other) {
        static_assert(N == Nn, "Vector size mismatch");
        for (std::size_t i = 0; i < N; i++) {
            data[i] = static_cast<T>(other[i]);
        }
    }

    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
  //...

};
}

示例(它们都应该编译)

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

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

// Example 3 (doesn't compile)
mu::Vector2D<int> e{1, 2};
mu::Vector<2, float> f{e};

// Example 4 (doesn't compile)
mu::Vector2D<int> g{1, 2};
mu::Vector2D<float> h{g};

错误(示例 3 和 4)

.../vector.h:63:27: error: no matching constructor for initialization of 'std::array<float, 2UL>'
Vector(TArgs... args) : data_({args...}) {}

代码试图调用错误的构造函数而不是包含类型转换的构造函数。我已经尝试 添加另一个约束 到 enable_if 项,但到目前为止没有好的结果。

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

是错误的,你可能想要&&Demo

std::is_convertible<TArgs, T>std::is_constructible<T, TArgs> 似乎更合适。

没有“标签”的可变参数构造函数是危险的顺便说一句,因为它可以轻松捕获复制构造函数。

@Jarod42 为我指明了正确的方向

对于两个参数,我需要的是 && 而不是 ||。但是,std::is_base_of 并没有真正执行我想要的。我将其替换为 std::is_same 以检查 ...TArgs 提供的所有参数是否实际上与 Vector 已经包含的参数 T.

属于同一类型

现在,只能通过给它提供确切的 N 个类型为 T

的参数来调用此构造函数
template <typename... TArgs,
       std::enable_if_t<sizeof...(TArgs) == N && 
                        (std::is_same_v<T, TArgs> && ...), int> = 0>
Vector(TArgs... args) : data_({args...}) {}