分配给数组子段:我在这里分配给Rvalue吗?如果是,我该如何修复它



希望有一天能让我的Fortran代码更容易移植到C++,我一直在开发一些表达式模板代码,以提供整个数组算术运算符,并能够从较长数组的指定部分复制和分配。不幸的是,如果没有一点样板代码,我想不出解决问题的方法,我会尽可能地减少这些代码。

首先,我有一个非常简单的"C风格数组结构",它封装了一个指针和一个长度,适合在我的混合语言应用程序的C、Fortran、C++和Java部分之间轻松地来回传递:

typedef struct {
int *p;    /*!< Pointer to the data */
int n;     /*!< The number of elements; int, not size_t, for Fortran compatibility  */
} int_array_C;
typedef struct {
float *p;    /*!< Pointer to the data */
int n;       /*!< The number of elements; int, not size_t, for Fortran compatibility  */
} float_array_C;
typedef struct {
double *p;   /*!< Pointer to the data */
int n;       /*!< The number of elements; int, not size_t, for Fortran compatibility  */
} double_array_C;

等等。然后,我根据维基百科中关于这个主题的条目中建议的方法定义了一些非常简单的表达模板:

template <typename E, typename T_C >
class VecExpression
{
typedef typename std::remove_pointer<decltype(T_C::p)>::type TT;
public:
//! Returns a const reference to the i'th element in the array
TT operator[] (int i) const noexcept 
{
return static_cast<E const&>(*this)[i];
}
//! Returns the total size of the array
int size() const noexcept
{
return static_cast<E const &>(*this).size();
}
operator E&() { return static_cast<E&>(*this); }
operator E const&() const { return static_cast<const E&>(*this); }
};
template <typename E1, typename T_C, typename E2, typename U_C  >
class VecSum : public VecExpression< VecSum<E1, T_C, E2, U_C>, T_C >
{
E1 const & _u;
E2 const & _v;
public:
//! Constructor taking two VecExpressions
VecSum(VecExpression<E1, T_C> const& u, VecExpression<E2, U_C> const &v) : _u(u), _v(v)
{
assert(u.size() == v.size());
}
int size() const noexcept { return _v.size(); }
auto operator[](int i) const
-> const decltype(_u[i] + _v[i]) { return _u[i] + _v[i]; }
// Automatically takes care of type promotion e.g. int to double
// according to the compiler's normal rules
};
template <typename E1, typename T_C, typename E2, typename U_C  >
VecSum<E1, T_C, E2, U_C> const operator+(VecExpression<E1, T_C> const &u,
VecExpression<E2, U_C> const &v)
{
return VecSum<E1, T_C, E2, U_C>(u, v);
}

为了给我一种操作C风格向量内容的方法,我定义了一些模板:一个处理预先存在的缓冲区中的数据,另一个通过使用std::vector:管理自己的内存

template <typename T_C> class nArray : public T_C, public VecExpression<nArray <T_C>, T_C >
{                                                  // This is the 'curiously recurring template
// pattern' (CRTP)
typedef typename std::remove_pointer<decltype(T_C::p)>::type TT;
struct startingIndex : public T_C
{
size_t start;
startingIndex(const T_C *initialiser) noexcept
{
*(static_cast<T_C *>(this)) = *initialiser;
}
nArray to(int element) noexcept
{
T_C::n = element - start + 1;
nArray<T_C> newArray(*(static_cast<T_C *>(this)));
return newArray;
}
};
public:
//! Constructor to create an nArray from an array_C, without copying its memory
nArray(T_C theArray) noexcept
{
T_C::p = theArray.p;
T_C::n = theArray.n;
}
//! Constructor to create an nArray from an ordinary C array, without copying its memory
template<std::size_t N>
nArray(TT (&theArray)[N]) noexcept
{
T_C::p = &theArray[0];
T_C::n = N;
}
nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) &
{
// Note that we cannot use the copy-and-swap idiom here because we don't have the means to
// construct a new temporary memory buffer. Therefore we have to handle the assignment-to-self
// case explicitly.
if (&source == this) return *this;
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i];
return *this;
}
//! Copy assignment operator taking a VecExpression of a different (but compatible) type
//! without allocating any new memory
template <typename E, typename U_C>
nArray operator=(VecExpression<E, U_C> const& source) &
{
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = static_cast<TT>(source[i]);
return *this;
}
//! Returns a non-const reference to the i'th element in the array
TT& operator[] (int i) noexcept
{
return T_C::p[i];
}
//! Returns a const reference to the i'th element in the array
const TT& operator[] (int i) const noexcept
{
return T_C::p[i];
}
startingIndex from(int element) const noexcept
{
startingIndex theNewArray(this);
theNewArray.p = &T_C::p[static_cast<size_t>(element)];
theNewArray.n = T_C::n - element;
theNewArray.start = element;
return theNewArray;
}
nArray to(int element) const noexcept
{
nArray theNewArray;
theNewArray.p = T_C::p;
theNewArray.n = element + 1;
return theNewArray;
}
// ... and a whole bunch of other functions
};
template <typename T_C> class nVector : public nArray<T_C>
{
typedef typename std::remove_pointer<decltype(T_C::p)>::type TT;
public:
template<std::size_t N>
nVector(TT (&source)[N]) 
{
contents.resize(N);
update_basetype();
std::copy(&source[0], &source[N], contents.begin());
}
// ...and a whole bunch of other constructors and assignment operators
// which echo those of nArray with the additional step of resizing the
// internal std::vector and copying the contents into it
private:
void update_basetype() noexcept
{
T_C::p = contents.size() > 0 ? contents.data() : nullptr;
T_C::n = contents.size();
}
std::vector<TT> contents;
};
typedef nArray<float_array_C> float_array;
typedef nVector<float_array_C> float_vector;
// ...and so on

呜呜!从这一点上,我可以做之类的事情

float a[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f };
float b[] = { 9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f };
float_array aArray(a);  // The array contents aren't copied, only
float_array bArray(b);  // the pointers
float_vector aVector = aArray.from(2);  // aVector is { 3.0f, 4.0f, 5.0f, 6.0f }
float_vector bVector = bArray.to(3);    // bVector is { 9.0f, 8.0f, 7.0f, 6.0f } 
float_vector cVector = aArray.from(2).to(4) + bArray.from(1).to(3);
// cVector is { 11.0f, 11.0f, 11.0f } 

他们工作很愉快。现在,我终于可以回答我的问题了。假设我想分配给一个数组子段,例如:

float_vector dVector(10);  // An empty 10-element array
dVector.from(3).to(5) = aArray.from(2).to(4) + bArray.from(1).to(3);

事实上,如果我在Visual C++2013中编译,这很好,但在gcc中则不然。分配时编译失败,并显示消息:

error: no match for 'operator=' (operand types are 'nArray<float_array_C>' and 'const VecSum<nArray<float_array_C>, float_array_C, nArray<float_array_C>, float_array_C>')
note: candidates are:
< ...skipping over a long list of utterly implausible options>
note: nArray<T_C>& nArray<T_C>::operator=(const VecExpression<nArray<T_C>, T_C>&) & [with T_C = float_array_C]
note: no known conversion for implicit 'this' parameter form 'nArray<float_array_C>' to 'nArray<float_array_C>&'

现在,当试图将临时对象分配给一个非常量引用时,或者当试图对一个Rvalue进行分配时,这个错误消息似乎出现在文献中,而Visual C++在这条规则方面比gcc更宽松(当然,gcc是符合标准的)。我可以理解为什么编译器可能会考虑

dVector.from(3).to(5)

作为一个Rvalue,尽管我已经竭尽全力防止它变成这样。例如,我的startingIndex::to()方法努力地按值而不是按引用返回nArray对象,如果我写

auto test1 = dVector.from(3).to(5);
auto test2 = aArray.from(2).to(4) + bArray.from(1).to(3);
test1 = test2;

那么这很好,编译器告诉我"test1"是一个"nArray<float_array_C>'(即float_array)。

所以,我的问题是:我真的为这里的Rvalue赋值有罪吗?如果我是,我怎么能停止这样做,同时仍然能够以这种方式,或者至少以某种类似的可读方式,对子数组进行赋值。我真的希望这能在C++中以某种方式完成,否则我想我需要回到Fortran领域,编写

dVector(3:5) = aArray(2:4) + bArray(1:3)

从此幸福地生活。

更新

根据Chris Dodd的建议,我尝试了几种不同的形式来构建另一个nArray构造函数,并确定了:

nArray && operator=(VecExpression<nArray<T_C>, T_C> const& source) &&
{
if (&source == this) return *this;
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i];
return *this;
}

(暂时不需要对任务进行自我检查)。gcc编译器似乎克服了这一点,但我得到的下一个错误是:

no known conversion for argument 1 from 'const VecSum<nArray<float_array_C>, float_array_C, nArray<float_array_C>, float_array_C>' to 'const VecExpression<nArray<float_array_C>, float_array_C>&'

这至少是一个不同的信息(总是进步的标志),但它仍然意味着基本问题是分配给错误类型的引用。不幸的是,我不确定我应该在哪里寻找这个:我经历了让我的运算符+返回VecSum<E1、T_C、E2、U_C>const&而不是按价值回报,但这完全没有什么区别。现在我又被卡住了,所以我的下一个策略是在我的Linux分区中安装clang,看看我是否从中得到了更有用的错误消息…

进一步更新:

Clang并没有特别的帮助;它只能说:

candidate function not viable: no known conversion from 'nArray<[...]>' to 'nArray<[...]>' for object argument

这并没有提供多少线索!

最终更新:

事实上,我很尴尬,回想起来,解决方案是多么明显。我所需要做的就是给我的分配操作员提供与普通移动分配操作员完全相同的表格:

nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) &&
{
// Better assignment-to-self check pending...
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i];
return *this;
}

这当然是Chris Dodd最初提出的建议,在Linux和Windows上的clang和gcc中运行得非常好。

您的nArray分配运算符:

nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) &

被明确定义为仅应用于左值nArray对象(该行的最后一个&),而不应用于右值对象,因此不能用于分配给临时切片,例如从dVector.from(3).to(5)获得的切片。您需要一个赋值运算符,该运算符具有对"this"的右值引用:

nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) &&

以便能够在这样的临时切片上调用这个赋值运算符。

请注意,您的&source == this自我分配检查是不够的。您可能有不同的nArray对象,它们引用相同的底层存储,具有不同的切片。考虑一下如果你尝试类似的东西会发生什么

aVector.from(3).to(7) = aVector.from(1).to(5)

相关内容

最新更新