【问题标题】:C++ SFINAE constructor accepting derived class tooC++ SFINAE 构造函数也接受派生类
【发布时间】:2021-02-15 11:48:50
【问题描述】:

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

我有一个Vector 和一个Vector2D 课程。前者应该包含一个允许按元素进行类型转换的构造函数。派生类也应该允许它。在某些情况下,它已经可以工作了(参见下面的示例),但我认为缺少一些 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 &lt;std::size_t Nn, typename Tt&gt; Vector(const Vector&lt;Nn, Tt&gt; &amp;other) 可以简单地为template &lt;typename Tt&gt; Vector(const Vector&lt;N, Tt&gt; &amp;other)
  • 我添加了 Nn 作为模板参数,这样我就可以添加一个 static_assert 来提供更清晰的错误消息,如“未找到匹配的构造函数”或某事。否则

标签: c++ templates inheritance constructor sfinae


【解决方案1】:
template <typename... TArgs,
        std::enable_if_t<sizeof...(TArgs) == N ||
                         (!std::is_base_of_v<Vector, TArgs> && ...), int> = 0>
  Vector(TArgs... args) : data({args...}) {}

错了,你可能想要&amp;&amp; Demo

std::is_convertible&lt;TArgs, T&gt;std::is_constructible&lt;T, TArgs&gt; 似乎更合适。

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

【讨论】:

  • 你是对的。我需要 && 而不是 ||对于这两个论点。但是,我也对这个构造函数施加了更重的限制,而不是std::is_base_of(见我的回答)
【解决方案2】:

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

对于这两个参数,我需要的是 &amp;&amp; 而不是 ||。但是,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...}) {}

【讨论】:

  • is_same 可能过于严格,mu::Vector&lt;2, float&gt; b{4.2f, 0} 会失败(0int 而不是 float)。
  • 是的,这很好。我不想允许像mu::Vector&lt;2,int&gt; b{0, 1.5f} 这样的代码应用隐式缩小并导致包含0,1 的向量
猜你喜欢
  • 1970-01-01
  • 2018-06-04
  • 2018-07-11
  • 2023-04-07
  • 1970-01-01
  • 2015-08-18
  • 2012-12-07
  • 2023-04-10
  • 1970-01-01
相关资源
最近更新 更多