英特尔C ++不能将`T **`转换为`T const * const *`,GCC可以
问题
现有代码的扩展
我有一个数字图书馆,设计时考虑到了一种“风味”。 现在我想概括一下。 基本的数据结构是一个“spinor”,它本身就是一个多维矩阵。 有很多功能需要这些旋钮的阵列。 广义函数需要为每种风味采用一个这样的spinor阵列。
说有一个功能,最低限度,做以下几点:
void copy_spinor(Spinor *out, const Spinor *in) {
std::cout << out << " " << in << "n";
}
我现在的概括是这样的:
void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {
std::cout << "Fwd: ";
copy_spinor(out[0], in[0]);
}
在真正的代码中,所有num_flav
都有一个循环,但这不是真的需要在这里演示。
据我所知,人们必须将其作为const Spinor *(in[num_flav])
读取,因此in
是指向可能为num_flav
元素的数组的指针(或另一个数量,因为foo[]
在函数参数中只是*foo
)类型指针到常量旋量。
问题在于它在使用Spinor *non_const[2]
(不带const
)时不能编译,请参阅我以前的问题。 从那里的答案我已经知道这不能编译,因为在函数copy_spinor
,可以使指针non_const[0]
指向一些Spinor *
const
数组。 那么non_const
会指向const
数据。 所以这是行不通的。
我的结论是,添加另一个const
会使其正确:
void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {}
当我现在将我的non_const
作为第二个参数传递时,函数不能in[0]
更改为任何内容,因为该指针现在是不可变的。 GCC 6.3对我来说很好。 现在使用英特尔C ++ 17进行生产,它不再工作。
最小的工作示例如下:
#include <cstdint>
typedef float Spinor[3][4][2][8];
template <uint8_t num_flav>
class Solver {
public:
void copy_spinor(Spinor *out, const Spinor *in) {
std::cout << out << " " << in << "n";
}
void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {
std::cout << "Fwd: ";
copy_spinor(out[0], in[0]);
}
};
int main(int argc, char **argv) {
Spinor *s1 = new Spinor[10];
Spinor *s2 = new Spinor[10];
Spinor *s1_a[1] = {s1};
Spinor *s2_a[1] = {s2};
Solver<1> s;
s.copy_spinor(s2_a, s1_a);
}
在GCC上,它显然解决了第二个copy_spinor
过载问题。 允许使用前一个non_const
角色的变量s1_a
作为参数。
英特尔C ++ 17的问题
但是,英特尔C ++ 17不接受:
$ icpc -Wall -pedantic const-spinor-const.cpp --std=c++11
const-spinor-const.cpp(23): error: no instance of overloaded function "Solver<num_flav>::copy_spinor [with num_flav=(uint8_t={unsigned char})' 01']" matches the argument list
argument types are: (Spinor *[1], Spinor *[1])
object type is: Solver<(uint8_t)' 01'>
s.copy_spinor(s2_a, s1_a);
^
const-spinor-const.cpp(11): note: this candidate was rejected because arguments do not match
void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {}
^
const-spinor-const.cpp(10): note: this candidate was rejected because arguments do not match
void copy_spinor(Spinor *out, const Spinor *in) {}
^
错误消息不是特别有用,因为它没有说明什么转换是不被允许的。 看起来const
是问题。
英特尔C ++有没有可能存在我想念的东西? 它缺少一个功能,还是我使用了非官方的GCC扩展? 这是英特尔C ++或GCC中的错误吗?
更新:该示例还与当前的Clang编译。
非模板类
当类Solver
不是模板类时,同样的问题仍然存在。 由于T a[2]
与函数参数中的T a[2]
和T *a
相同,我也可以像这样写函数,而不需要uint8_t num_flav
:
void copy_spinor(Spinor *out[], const Spinor *const in[]) {
std::cout << "Fwd: ";
copy_spinor(out[0], in[0]);
}
错误是一样的。
免费功能
非会员非朋友非模板功能也会发生同样的问题:
void free_spinor(Spinor *out, const Spinor *in) {
std::cout << out << " " << in << "n";
}
void free_spinor(Spinor *out[], const Spinor *const in[]) {
std::cout << "Fwd: ";
free_spinor(out[0], in[0]);
}
错误消息是一样的:
$ icpc -Wall -pedantic const-spinor-const.cpp --std=c++11
const-spinor-const.cpp(97): error: no instance of overloaded function "free_spinor" matches the argument list
argument types are: (Spinor *[1], Spinor *[1])
free_spinor(s2_a, s1_a);
^
const-spinor-const.cpp(30): note: this candidate was rejected because arguments do not match
void free_spinor(Spinor *out[], const Spinor *const in[]) {
^
const-spinor-const.cpp(26): note: this candidate was rejected because arguments do not match
void free_spinor(Spinor *out, const Spinor *in) {
^
解决方案尝试
为了在生产中运行代码,我看到以下选项。 他们没有一个特别有吸引力。
什么是前进的好方法? 我可以改变我想要的所有新功能,但我希望尽可能避免接触呼叫者代码。
Const包装器
当我在main
函数中改变s1_a
的定义使其具有两个const
,它会编译:
const Spinor *const s1_a[1] = {s1};
然后函数copy_spinor
被调用正确的参数类型。
通用代码的每个用户都必须为每个函数调用编写这些const
包装器。 这将变得非常混乱。
删除Const正确性。
我可以从函数参数参数中删除最左侧的const
。 这两个编译器都干净地编译。 但是,我确实希望证明我没有更改该数组中的任何内容,因此它的值应该是const
。
部分解决方案是使用一些预处理器常量,仅删除英特尔编译器的const
。
#ifdef __INTEL_COMPILER
#define ICPC_CONST
#else
#define ICPC_CONST const
#endif
也许用户已经将一些spinor定义为const。 然后我被卡住了,需要在那里建立const:
const Spinor *s3_a[1] = {s3};
s.copy_spinor(s2_a, s3_a);
添加一个const
比删除它更容易,所以这个解决方案非常缺乏。 上游作者可能会拒绝我的更改,因为他的代码发生了变化。
为每个函数添加一个非const重载
为每个功能添加一个过载是可能的。 我有两个广义函数的变体,第二个在我使用英特尔编译器时启用:
void copy_spinor(Spinor *out, const Spinor *in) {
std::cout << out << " " << in << "n";
}
void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {
std::cout << "Fwd: ";
copy_spinor(out[0], in[0]);
}
#ifdef __INTEL_COMPILER
void copy_spinor(Spinor *out[num_flav], Spinor *const in[num_flav]) {
std::cout << "Fwd2: ";
copy_spinor(out[0], in[0]);
}
#endif
这很好,只是有一些代码重复。 由于我添加的功能只是重复使用现有的功能,所以不会有太多的代码重复。 仍然违反了DRY原则。
另一个缺点是重载次数是2^N
,其中N是一个const *
参数的数量。 有几个函数需要这样的三个参数,因此我需要八个副本。
让模板推导出稳定性
使用模板可以将const Spinor
和Spinor
抽象出来。 我可以将函数编写为模板,以便S
可以是数据类型。 使用static_assert
会给出更多的信息错误信息。
template <typename S>
void copy_spinor(Spinor *out[num_flav], S *const in[num_flav]) {
static_assert(std::is_same<Spinor, S>::value ||
std::is_same<const Spinor, S>::value,
"Template parameter must be `const Spinor` or `Spinor`.");
std::cout << "Fwd: ";
copy_spinor(out[0], in[0]);
}
理想情况下,我想指定S
只能是Spinor
或const Spinor
。 也许这对C ++ 14及更高版本是可能的,我必须坚持使用C ++ 11。
这个解决方案看起来很干净,我可以为函数中的每个参数添加一个模板参数和一个断言。 这将会很好地扩展,并且没有代码重复。 唯一的缺点可能是编译时间较长(已经很长,但不是很重要),而且不太有用的错误消息(希望可以用static_assert
覆盖)。
使用int **
调用它时的错误消息是GCC的以下内容:
const-spinor-const.cpp: In instantiation of 'void Solver<num_flav>::t_copy_spinor(float (**)[3][4][2][8], S* const*) [with S = int; unsigned char num_flav = 1u; Spinor = float [3][4][2][8]]':
const-spinor-const.cpp:86:36: required from here
const-spinor-const.cpp:40:9: error: static assertion failed: Template parameter must be `const Spinor` or `Spinor`.
static_assert(std::is_same<Spinor, S>::value ||
^~~~~~~~~~~~~
const-spinor-const.cpp:45:20: error: no matching function for call to 'Solver<1u>::copy_spinor(float (*&)[3][4][2][8], int* const&)'
copy_spinor(out[0], in[0]);
~~~~~~~~~~~^~~~~~~~~~~~~~~
const-spinor-const.cpp:29:10: note: candidate: void Solver<num_flav>::copy_spinor(float (*)[3][4][2][8], const float (*)[3][4][2][8]) [with unsigned char num_flav = 1u; Spinor = float [3][4][2][8]]
void copy_spinor(Spinor *out, const Spinor *in) {
^~~~~~~~~~~
const-spinor-const.cpp:29:10: note: no known conversion for argument 2 from 'int* const' to 'const float (*)[3][4][2][8]'
const-spinor-const.cpp:33:10: note: candidate: void Solver<num_flav>::copy_spinor(float (**)[3][4][2][8], const float (* const*)[3][4][2][8]) [with unsigned char num_flav = 1u; Spinor = float [3][4][2][8]]
void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {
^~~~~~~~~~~
const-spinor-const.cpp:33:10: note: no known conversion for argument 1 from 'float (*)[3][4][2][8]' to 'float (**)[3][4][2][8]'
在评论中指出使用enable_if
。 这样,我的功能如下所示:
template <typename S>
typename std::enable_if<std::is_same<const Spinor, const S>::value,
void>::type
t2_copy_spinor(Spinor *out[num_flav], S *const in[num_flav]) {
std::cout << "Fwd: " << typeid(S).name() << " " << typeName<S>() << " ";
copy_spinor(out[0], in[0]);
}
这个时间更短,也许更为顺利。 但是,错误消息不再包含我的手写消息。 至少在函数copy_spinor
中不会发生错误,但是在调用站点中,所以用户知道它出错的地方。 这也许更好。 而且enable_if
有些自我解释,至少对更有经验的模板用户来说。
const-spinor-const.cpp: In function 'int main(int, char**)':
const-spinor-const.cpp:86:37: error: no matching function for call to 'Solver<1u>::t2_copy_spinor(float (* [1])[3][4][2][8], int* [2])'
s.t2_copy_spinor(s2_a, int_array);
^
const-spinor-const.cpp:51:5: note: candidate: template<class S> typename std::enable_if<std::is_same<const float [3][4][2][8], const S>::value, void>::type Solver<num_flav>::t2_copy_spinor(float (**)[3][4][2][8], S* const*) [with S = S; unsigned char num_flav = 1u]
t2_copy_spinor(Spinor *out[num_flav], S *const in[num_flav]) {
^~~~~~~~~~~~~~
const-spinor-const.cpp:51:5: note: template argument deduction/substitution failed:
const-spinor-const.cpp: In substitution of 'template<class S> typename std::enable_if<std::is_same<const float [3][4][2][8], const S>::value, void>::type Solver<num_flav>::t2_copy_spinor(float (**)[3][4][2][8], S* const*) [with S = int]':
const-spinor-const.cpp:86:37: required from here
const-spinor-const.cpp:51:5: error: no type named 'type' in 'struct std::enable_if<false, void>'
enable_if
解决方案看起来比static_assert
变体更好。
GCC和叮当就在这里,英特尔C ++是错误的。
标准的相关部分是资格转换 [conv.qual]
(节号可以是4.4或4.5)。 措辞已经改变了C ++ 11和C ++ 1Z之间(成为C ++ 17)......但你的代码添加const
在开始与浅的多层次的,是允许在所有版本(C ++ 03, 11,14,1z)。
需要注意的一点是,多级常量规则现在适用于指针数组,以前它只适用于多个指针。 但是我们实际上正在处理一个多指针,因为在参数声明中找到的数组语法具有指针语义。
不过,尝试一下也许值得
void copy_spinor(Spinor **out, const Spinor *const *in)
以防编译器在应用规则之前混淆/未能在数组参数列表中将数组类型调整为指针类型。
链接地址: http://www.djcxy.com/p/66509.html上一篇: Intel C++ cannot convert `T **` to `T const * const *`, GCC can
下一篇: Why decltype expressions in return types have to be mangled in the symbol name?