为什么我需要三个嵌套的大括号来调用赋值运算符,将const引用到二维数组



我有一个模板类,其赋值运算符的代码如下所示,该运算符接受对二维数组的常量左值引用。

template<typename T> 
class matrix{
...
template< size_t Nr, size_t Nc>
matrix& operator=(const T(&arg)[Nr][Nc]) :rows(Nr), cols(Nc) {
const T* p = &arg[0][0];
const T* pEnd = p + (Nr * Nc);
vdata.reserve(sizeof(T) * Nr * Nc);
std::copy(p, pEnd, std::back_inserter(vdata));
return *this;
}
...
size_t rows;
size_t cols;
std::vector<T> vdata;
};

我可以使用以下语法调用这个赋值运算符,

matrix<double>& mweights = get reference ...
mweights = {{{ 20.0, 20.0,-10.0},
{-20.0,-20.0, 30.0}}};

然而,我第一次尝试

mweights = {{ 20.0, 20.0,-10.0},
{-20.0,-20.0, 30.0}};

导致编译器(选择了C++17标准的MSVC2019(错误消息"matrix::operator=':构造函数初始值设定项列表只允许用于构造函数定义"现代C++中所有不同的对象构造和初始化方法都可能有点令人困惑(礼貌地说(,我不确定编译器到底想告诉我什么。但我发现我让它很不开心。。。

在这种情况下,我希望编译器将我的braked初始值设定项视为对双精度二维数组的右值引用,并将赋值运算符的const左值引用参数绑定到它。很明显,我错过了一些东西(或者如果这只是冰山一角的话,可能会错过很多东西(

我的问题是1(为什么第一个语法有效?和2(为什么第二个没有?

更新

感谢Ted注意到我的复制粘贴错误:rows((,cols((这就是编译器消息的全部内容。

因此,同一个类还有一个采用相同类型参数的模板化构造函数,以及一个移动赋值运算符。因此,当我添加额外的大括号时,我绕过了赋值运算符(因此编译器停止抱怨,因为模板函数不再被实例化(,而是使用模板化构造函数创建了一个临时构造函数,并将其传递给move赋值运算符!!

从赋值中删除非法的成员初始值设定项可以消除编译器错误,并且只使用两个大括号的调用可以正常工作

我把另外两个函数和更正后的(只是部分原因是下面提到的UB仍然存在(赋值运算符粘贴在下面以供参考。

// ctor for initializing a matrix with syntax 
// auto m = matrix<T>({{1,2,3},{4,5,6}}); or
// auto m = matrix<T>{{{1,2,3},{4,5,6}}};
template< size_t Nr, size_t Nc>
matrix(const T(&arg)[Nr][Nc]) :rows(Nr), cols(Nc) {
const T* p = &arg[0][0];
const T* pEnd = p + (Nr * Nc);
vdata.reserve(sizeof(T) * Nr * Nc);
std::copy(p, pEnd, std::back_inserter(vdata));
}
//move assignment (cannot be template)
matrix& operator = (matrix&& arg)
{
// std::cout << "n matrix move assignment to " << this;
rows = arg.rows;
cols = arg.cols;
vdata = std::move(arg.vdata);
arg.rows = 0;
arg.cols = 0;
return *this;
}
// assignment from arrays.
template< size_t Nr, size_t Nc>
matrix& operator=(const T(&arg)[Nr][Nc]) {
rows = Nr;
cols = Nc;   
const T* p = &arg[0][0];
const T* pEnd = p + (Nr * Nc);
vdata.clear();
vdata.reserve(Nr * Nc);
std::copy(p, pEnd, std::back_inserter(vdata));
return *this;
}

非常感谢!

问题的更新部分已经给出了答案。

为了完整性,(并且不干扰SO记账(我将发布一个回复。使用{{{},{}}的第一个语法实际上根本没有使用模板赋值运算符,并且在隐藏编译器最初抱怨的语法错误的过程中。

相反,编译器使用模板化构造函数生成代码来创建临时的,该构造函数使用与赋值运算符相同类型的参数,然后使用移动赋值(如果不可用,则会使用复制赋值(来执行相同的任务。

第二种语法{{},{}}在修复赋值运算符后即可工作。

此外,请注意,正如评论中所指出的,上面的代码中(至少(还有两个问题。

  1. UB在指针访问二维数组中
  2. vdata.reserve调用的大小不应为(T(

再次感谢您的评论!

最新更新