如果类构造函数的大括号闭合列表的大小错误,则编译时出错
Compile time error if brace-closed list is the wrong size for class constructor
我正试图编写一个基于数学向量的类:
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.}
工作得很好,但这会给用户带来负担,让他们记住只提供浮点文字。
您可以使构造函数成为可变模板,以便可以使用任何条件:
#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
初始化器来解决"缩小转换"问题。哦,你可以免费得到任务。
正如@M.M所提到的,不幸的是,这个解决方案破坏了复制构建。您可以通过在变量"数组"大小上添加enable_if
来解决它,如上所示。当然,您需要注意不要破坏赋值/复制构造和单元素向量,这可以通过为这些特殊情况添加两个额外的构造函数来弥补。
这段代码似乎对构造函数和赋值运算符都有效:
#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[]
的一行使用了我所说的可变模板逗号运算符破解。这个黑客有很多用途,但在这里它让我们避免了double tmp[] { args... };
的缩小转换问题。
(不过,TBH,结合rubvenvb的想法并使用double tmp[] { static_cast<double>(args)... };
会更简单)
- 用MacOS Mojave编译C++:致命错误:mpi.h:没有这样的文件或目录
- std::is_base_of表示ctor编译错误
- 我的项目不会像"undefined reference to `grpc::g_core_codegen_interface'"那样使用未定义的引用错误进行编译
- 在没有定义返回类型的函数中返回布尔值,并将结果保存在无错误的char编译中-为什么
- Qt5:使用QCommandLineParser类时出现奇怪的编译错误
- Qt Cmake 错误编译"GuiSupportQt not found"
- Opengl 精度转换错误编译错误 E0415
- 库将ARM架构错误编译为架构X64
- RT 音频 Mac 错误 g++ 编译错误
- 错误编译Boost.log
- 错误编译QT创建者 / QT窗口小部件示例
- 错误编译MIPS32
- Visual Studio 2013 中的错误(编译和运行代码)
- 链接错误编译qt项目在visual 2010
- 无法用模板错误编译nsgmls
- 奇怪的错误.编译失败
- 如果有人调用c++中的方法,则强制错误(编译时)
- 来自autoconf测试的错误编译命令
- 时间限制超出错误C++编译
- SFML 2.3 和 CodeBlocks 错误编译