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.}
工作正常,但这会给用户带来负担,只记得提供浮点文字.
您可以使构造函数成为可变参数模板,以便可以使用任何条件:
#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)
它可以为您提供自定义错误消息,容易适应/可扩展的失败条件,并通过有效地将初始化转发到初始化程序,从而摆脱"缩小转换"问题,std::array
就像您首先想做的那样.哦,你可以免费获得作业.
正如@MM所提到的,不幸的是,这种解决方案破坏了复制结构.您可以通过enable_if
在变量参数"array"上添加一个大小来解决它,如上所示.当然,你需要小心不要破坏赋值/复制构造和单元素向量,这可以通过为这些特殊情况添加两个额外的构造函数来解决.
对于构造函数和赋值运算符,此代码似乎都有效:
#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)... };
会更简单)