将双精度数组重新解释为std::complex<double>数组。

8
C++11标准中提到,将std::complex<double>重新解释为double,可以这样做:
对于名为pcomplex<T>数组的任何元素的指针和任何有效的数组索引ireinterpret_cast<T*>(p)[2*i]是复数p[i]的实部,reinterpret_cast<T*>(p)[2*i + 1]是复数p[i]的虚部。该要求的目的是保持C++库复数类型和C语言复数类型(及其数组)之间的二进制兼容性,它们具有相同的对象表示要求。
反过来重新解释是否正确呢?也就是说,执行类似于std::complex<double> *cppComplexArray = reinterpret_cast<std::complex<double> *>(cDoublesArray)这样的操作是否安全?其中cDoublesArray的类型为double *,长度为2 * n?如果长度为奇数(2 * n + 1),可能会遇到什么问题?

1
显然,反向重新解释也应该有效 - 如果您有一个双精度数组中的偶数个元素。但是,如果您正在寻找使用C++标准中的某些措辞来证明它,请添加“language-lawyer”标签。 - Eugene
很确定如果它在向前是有效的,那么在向后也是有效的。 - NathanOliver
如果std::complex本身是一个表现良好的类型...也许可以。否则,C++在其对象模型中有一些黑暗角落(随着年份的变化而变化)。它可能是不合规的C++17,但是在C++20中是有效的。虽然我很讨厌正式地尝试证明它。 - StoryTeller - Unslander Monica
反向重新解释是真的吗?不,使用指针算术中的cppComplexArray将违反[expr.add]/6 - Language Lawyer
C++11标准规定:我已经打开了C++11草案https://timsong-cpp.github.io/cppwp/n3337/complex.numbers,但在那里找不到“意图”一词。如果你在引用cppreference,请不要说它是标准,因为它并不是。 - Language Lawyer
2个回答

1
实际上,由于正向重新解释的强制性约束(请参见cppreference上的std::complex实现注释),反向重新解释可能大部分时间都能够正常工作。
然而,我并不完全确定这样的反向重新解释在理论上总是有效:
假设一个荒谬和假想的复数库实现,该库将维护活动复数对象的地址列表(例如,用于调试目的)。 这个(可能是静态的)列表将由复数构造函数和析构函数维护。
这个库中的每个复数操作都会验证其操作数是否在列表中。
虽然正向重新解释可以工作(复数对象已经构建良好,其部件可以用作double),但反向重新解释将无法工作(例如,尽管布局兼容,您将重新解释一对double作为复数,如果您对它们执行任何复数操作,则会失败,因为复数没有正确构建,即其地址不在列表中)。
如上所述,这个复杂的库可能是一个愚蠢的想法。但是这样的库可以被实现并符合标准规范。这足以证明在理论上不存在反向保证。
假设我们有一个实现,其中反向重新解释工作。缺失的最后一列将导致访问超出边界的部分。这将导致未定义行为。
附加阅读:标准委员会的这篇工作论文请求一个通用的可转换特性,可以将reinterpret_cast在其他类型上推广到complex的行为。它在第4节中解释了复杂情况,并解释了编译器需要进行的特殊处理,以使其在complex本身没有由数组实现时正常工作。

1
好的,我认为最好还是保持将给定的“doubles”逐个复制到手动创建的复数值数组中。 - Drobot Viktor
顺便问一下,如果我创建一个复杂数组的“双重表示”,例如 double *dblArr = reinterpret_cast<double *>(cmplxArr),然后将此指针传递给外部函数(例如存储结果的 C 函数),那么原始复杂存储中的数据是否会被修改?我的意思是,如果原始的 cmplxArr 包含 3 个复杂的双精度值 (1.0+1.0i, -2.5+4.0i, 1.2-0.1i),而 C 函数只是将给定的 dblArr 乘以 2,那么 cmplxArr 的内容是否会变成这样 (2.0+2.0i, -5.0+8.0i, 2.4-0.2i)? - Drobot Viktor
刚刚测试了一下,运行良好。但是我不确定这是否是可移植和定义良好的方法。更新:重新阅读标准中的引用,似乎对于任何有效的索引和指针,行为应该是定义良好的。 - Drobot Viktor
1
@DrobotViktor 是的,如果你传递了指向双精度数组的指针,并且你的函数修改了所指对象的内容,则原始数据将被修改。这是可以接受的:这是重新解释保证的结果,因此您可以完全依赖它。 - Christophe
1
@LanguageLawyer 严格来说,C对象≠C++对象,C指针≠C++指针等。混合使用C和C++代码是C/C++宇宙的一个特性,对于函数,有extern "C",通常,接受结构体参数的C函数也定义了该结构体。如果C++代码现在实例化这样的结构体并将指向C函数的指针作为参数传递,这也是100%可以的。您的措辞没有反映出这一点。 - BitTickler
显示剩余2条评论

1
“反向重新解释”是真的吗?我的意思是执行类似于以下内容是否安全:std::complex<double> *cppComplexArray = reinterpret_cast<std::complex<double> *>(cDoublesArray)
转换/初始化本身是安全的,使用结果仿佛指向std::complex<double>数组的元素则不安全。
cDoublesArray(或应用于它的数组到指针转换,如果cDoublesArray表示double数组)point指向double数组的第一个元素时,reinterpret_cast<std::complex<double>*>(cDoublesArray)也是如此(具有相同的值)。

使用类型为std::complex<double>*的表达式,其值为指向double类型对象的指针(例如reinterpret_cast<std::complex<double>*>(cDoublesArray)cppComplexArray),在指针算术运算(例如cppComplexArray + 0)中将违反[expr.add]/6

对于加法或减法,如果表达式PQ具有类型“指向cvT”的指针,其中T和数组元素类型不相似,则行为未定义。

(Tstd::complex<double>,这里的数组元素类型是 double,它们不是相似的)


1
有趣。指针算术的一般规则确实非常严格(即相同类型的指针在同一数组范围内)。然而,有效重新解释转换的要求和意图“保持C++库复数类型和C语言复数类型(及其数组)之间的二进制兼容性”是否绕过了此规则?(例如,请参见此分析第4节) - Christophe
1
@Christophe 当然,通常的[expr.add]规则被绕过了,但仅适用于形式为reinterpret_cast<T*>(p)[2*i]reinterpret_cast<T*>(p)[2*i + 1]的表达式,其中p是指向类型为std :: complex <T>的数组元素的指针,而i是整数类型的表达式。 - Language Lawyer
重新解释指针的一般规则难道不允许在其他表达式中使用获得的指针值吗?而在复杂情况下这些原则的例外是否会导致不一致性呢? - Christophe
@Christophe _重新解释指针的一般规则不允许在其他表达式中使用所获得的指针值吗?_无论您喜欢使用reinterpret_cast<T*>(p),但在某些情况下可能会有未定义行为。_在复杂情况下这些原则的例外是否会导致不一致?_与什么不一致? - Language Lawyer
好的,看起来使用直接反向重新解释并不是一种可移植和明确定义的方式。但是如果直接在重新解释为double *源数组std::complex<double> x*上进行操作呢(请参见我对Christophe答案的评论)?似乎标准规定直接操作应该是可以的,因为意图是支持复杂数据类型在C和C++中具有相同的对象表示,并且在C中,从c99开始(我使用c11),复杂值存储为两个相邻的子值。我尝试通过修改C函数中的double *数组,原始的C++复杂数据已经发生了变化。 - Drobot Viktor
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接