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

Ant*_*ant 9 c++ templates c++11 list-initialization

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

Vector<3> a = {1,2,3};
a = {3,5,1};
Run Code Online (Sandbox Code Playgroud)

用户期望编写这样的代码是很自然的,对吧?但是,如果我使用错误大小的初始化列表,我希望发生编译时错误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
Run Code Online (Sandbox Code Playgroud)

我的第一个想法是使用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>) 
Run Code Online (Sandbox Code Playgroud)

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

template <typename... Args> Vector(Args... li): x({li...}){
    static_assert(sizeof...(li) == N);
}
Run Code Online (Sandbox Code Playgroud)

它必须是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]|
Run Code Online (Sandbox Code Playgroud)

大概违反[8.5.4]/7

缩小转换是隐式转换

- 从整数类型或无范围枚举类型到浮点类型,除非源是常量表达式,转换后的实际值将适合目标类型,并在转换回原始类型时生成原始值,要么

来自扩展的参数li...不是常量表达式,因此产生变窄的转换误差.据我所知,甚至不可能将函数参数作为常量表达式(也不是很有意义吗?).所以我不确定如何沿着这条路走下去.显然Vector<2> a = {2.,3.}工作正常,但这会给用户带来负担,只记得提供浮点文字.

rub*_*nvb 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;
}
Run Code Online (Sandbox Code Playgroud)

coliru上的实例.

它可以为您提供自定义错误消息,容易适应/可扩展的失败条件,并通过有效地将初始化转发到初始化程序,从而摆脱"缩小转换"问题,std::array就像您首先想做的那样.哦,你可以免费获得作业.

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


M.M*_*M.M 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
}
Run Code Online (Sandbox Code Playgroud)

赋值运算符是有效的,因为构造函数是隐式的,因此v = bla尝试转换bla以匹配唯一的定义operator=.

我创建了第一个参数double d而不是仅仅使用所有可变参数args,以避免all-variadic-args构造函数捕获应该是复制构造的调用的问题.

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

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