如果 class 构造函数的大括号闭合列表大小错误,则编译时错误

Compile time error if brace-closed list is the wrong size for class constructor

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

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};

用户自然会期望编写这样的代码,对吧?但是,如果我使用错误大小的初始化列表,我希望出现 compile-time 错误,就像 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 作为 constructor/operator 参数,这样会发生隐式转换,然后构造函数会从 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

A narrowing conversion is an implicit conversion

— from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or

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

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

#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;
}

Live example on coliru.

它会为您提供自定义错误消息,很容易 adaptable/extensible 失败条件,并通过有效地将初始化转发给您想要的 std::array 初始化程序来摆脱 "narrowing conversion" 问题首先要做的。哦,你还可以免费获得作业。

正如@M.M 提到的,不幸的是,这个解决方案破坏了复制构造。您可以通过在可变参数 "array" 大小上添加 enable_if 来解决它,如上所示。当然你需要小心不要破坏 assignment/copy-construction 和单元素向量,这是通过为这些特殊情况添加两个额外的构造函数来补救的。

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

#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 有很多用途,但在这里它让我们避免了 double tmp[] { args... }; 的缩小转换问题。

(尽管如此,结合 rubvenvb 的想法并使用 double tmp[] { static_cast<double>(args)... }; 会更简单)