9

我正在尝试基于数学向量编写一个类:

template <unsigned N> class Vector{
public:
    Vector() = default;
    Vector(std::initializer_list<double> li) { *this = li;}
    Vector& operator=(std::initializer_list<double>);

private:
    std::array<double, N> x = {}
}

template <unsigned N> inline Vector<N>& Vector<N>::operator=(std::initializer_list<double> li){
     if(N != li.size()) throw std::length_error("Attempt to initialise Vector with an initializer_list of different size.");
     std::copy(li.begin(), li.end(), x.begin());
     return *this;
}

我希望能够编写这样的代码;

Vector<3> a = {1,2,3};
a = {3,5,1};

用户期望编写这样的代码是很自然的,对吧?但是,如果我使用错误大小的初始值设定项列表,我希望发生编译时错误,就像这样std::array做一样。

 std::array<double, 3> a = {2,4,2,4} //compile time error
 Vector<3> a = {3,5,1,5} //run-time error as of right now

我的第一个想法是std::array用作构造函数/操作符参数,这样会发生隐式转换,然后构造函数会劫持std::array编译时错误。当然,除了我只能写这样的代码:

Vector<3> a({2,3,2}); //fine
Vector<3> b = {2,4,2}; //error, requires two user-defined conversions (list -> array<double,3> -> Vector<3>) 

我想也许可以使用 Variadic 成员模板:

template <typename... Args> Vector(Args... li): x({li...}){
    static_assert(sizeof...(li) == N);
}

它必须是,typename...而不是double...因为非类型参数必须是整数类型。但后来我遇到了一个缩小的转换错误

Vector<2> a = {3,2} //error: narrowing conversion of 'li#0' from 'int' to 'double' inside { } [-Wnarrowing]|
 //error: narrowing conversion of 'li#1' from 'int' to 'double' inside { } [-Wnarrowing]|

大概是因为违反 [8.5.4]/7

缩小转换是隐式转换

— 从整数类型或无作用域枚举类型到浮点类型,除非源是常量表达式并且转换后的实际值将适合目标类型并且在转换回原始类型时将产生原始值,或者

扩展的参数li...不是常量表达式,因此会产生缩小转换错误。据我所知,甚至不可能将函数参数作为常量表达式(也没有多大意义?)。所以我不确定如何沿着这条路线继续前进。显然Vector<2> a = {2.,3.}工作正常,但这给用户带来了负担,要记住只提供浮点文字。

4

2 回答 2

5

您可以使您的构造函数成为可变参数模板,以便可以使用任何条件:

#include <array>
#include <cstddef>

template<typename T, std::size_t N>
class vector
{
public:
    vector(T&& value)
    : data{static_cast<T>(value)}
    {}
    template<typename U>
    vector(const vector<U,N>& v)
    {
      std::copy(begin(v.data), end(v.data),
                begin(data));
    }
    template<typename U>
    vector(const vector<U,N>& v)
    {
        std::copy(begin(v.data), end(v.data),
                  begin(data));
    }
    template<typename... U,
             typename = typename std::enable_if<sizeof...(U)-1>::type>
    vector(U&&... values)
    : data{static_cast<T>(values)...}
    {
        static_assert(sizeof...(values) == N, "wrong size");
    }
    std::array<T,N> data;
};

int main()
{
    vector<int, 3> v = {1,2,3};
    vector<double, 4> vv = {5,4,3,2};

    vv = {1,2,3,4};

    //vector<float, 3> vf = {1,2,3,4}; // fails to compile
    vector<float,3> vf = v;
}

关于 coliru 的实时示例

它为您提供自定义错误消息,轻松适应/可扩展的失败条件,并通过有效地将初始化转发到初始化程序来摆脱“缩小转换”问题,std::array就像您首先想要做的那样。哦,你可以免费获得任务。

正如@MM 所提到的,不幸的是,这个解决方案破坏了复制结构。您可以通过在可变参数“数组”大小上添加一个来解决它,enable_if如上所示。当然,您需要注意不要破坏赋值/复制构造和单元素向量,这可以通过为这些特殊情况添加两个额外的构造函数来解决。

于 2015-11-25T21:24:38.233 回答
5

这段代码似乎对构造函数和赋值运算符都有效:

#include <array>

template<size_t N>
struct Vector
{
    Vector() = default;

    template<typename...Args>
    Vector(double d, Args... args)
    {
        static_assert(sizeof...(args) == N-1, "wrong args count");

        size_t idx = 0;
        auto vhelp = [&](double d) { x[idx++] = d; };
        vhelp(d);
        double tmp[] { (vhelp(args), 1.0)... };
    }

    Vector &operator=(Vector const &other) = default;

private:
    std::array<double, N> x = {};
};

int main()
{
    Vector<5> v = { 1,2,3,4,5 };
    v = { 3,4,5,6,7 };

    Vector<1> w = { 1,2 };  // error
}

赋值运算符起作用是因为构造函数是隐式的,因此v = bla尝试转换bla以匹配operator=.

我做了第一个参数double d而不是只使用所有可变参数,以避免所有可变参数构造函数捕获应该是复制构造的调用的问题。

涉及的行double tmp[]使用了我所说的可变参数模板逗号运算符 hack。这个 hack 有很多用途,但在这里它可以让我们避免缩小转换问题double tmp[] { args... };

(虽然TBH,结合rubvenvb的想法和使用double tmp[] { static_cast<double>(args)... };会更简单)

于 2015-11-25T21:28:52.040 回答