英特尔C ++无法将“T **”转换为“T const * const *”,GCC可以

时间:2017-03-28 09:58:04

标签: c++11 gcc icc

问题

扩展现有守则

我有一个数字库,设计时考虑到了一种“味道”。 现在我想概括一下。基本数据结构是“旋转” 本身是一个多维矩阵。有很多功能 这些旋转器的阵列。广义函数需要采用一个这样的旋转器 每种口味的数组。

假设有一个功能,最低限度地执行以下操作:

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) 指针给const-旋量。

问题是使用Spinor *non_const[2]时无法编译 (没有const),请参阅my earlier question。从答案那里我 已经知道这不能编译,因为在函数内 copy_spinor,指针non_const[0]可以指向某些指针 const的{​​{1}}数组。然后Spinor *会指向non_const个数据。 因此,这不起作用。

我的结论是,添加另一个 const会使其更正:

const

当我现在将void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {} 作为第二个参数传递时,函数不能 将non_const更改为任何内容,因为该指针现在是不可变的。这有 GCC 6.3让我很好。现在开始使用英特尔C ++ 17进行生产 不再有用了。

最小的工作示例如下:

in[0]

在GCC上,它显然解决了第二个#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); } 重载。该 变量copy_spinor允许使用前一个s1_a的角色 一个论点。

英特尔C ++ 17的问题

然而,

英特尔C ++ 17不接受:

non_const

错误消息不是特别有用,因为它没有说明什么 不允许转换。似乎$ 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})'\001']" matches the argument list argument types are: (Spinor *[1], Spinor *[1]) object type is: Solver<(uint8_t)'\001'> 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) {} ^ 似乎是问题所在。

英特尔C ++可能会遗漏一些东西吗?是否缺少功能或 我是否使用了非官方的GCC扩展?这是英特尔C ++中的错误吗? GCC?

更新:该示例还会编译当前的Clang。

非模板类

当类const不是模板类时,同样的问题也会持续存在。 由于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]);
}

解决方案尝试

为了在生产中运行代码,我看到以下选项。没有 它们特别有吸引力。

前进的好方法是什么?我可以改变我的新功能 想要,但我希望尽可能避免触及来电代码。

Const Wrappers

当我将$ 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) { ^ 函数中s1_a的定义更改为两个时 main,它编译:

const

然后使用正确的参数类型调用函数const Spinor *const s1_a[1] = {s1};

通用代码的每个用户都必须编写copy_spinor 每个函数调用的包装器。这将变得非常混乱。

删除Const Correctness。

我可以从函数参数参数中删除最左边的const。那 在两个编译器上干净地编译。但是,我确实希望记录我的情况 不更改该数组中的任何内容,因此其值应为const

部分解决方案是使用一些预处理器常量来删除 const仅适用于英特尔编译器。

const

也许用户已经将一些spinor定义为const。然后我被困住了,需要 在那里有const:

#ifdef __INTEL_COMPILER
#define ICPC_CONST
#else
#define ICPC_CONST const
#endif

添加const Spinor *s3_a[1] = {s3}; s.copy_spinor(s2_a, s3_a); 比删除它更容易,所以这个解决方案就是 非常缺乏。上游作者也可能拒绝我的更改 他代码中的变化。

为每个函数

添加非const重载

可以为每个函数添加重载。我有两种变体 广义函数,第二个在我使用时启用 英特尔编译器:

const

效果很好,只有一些重复的代码。自从我加入 函数只是重用现有函数,它不是那么多代码 复制。仍然违反DRY原则。

另一个缺点是重载次数是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 ,其中N是 2^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]); } 只能是SSpinor。也许这对C ++ 14及以后是可能的,我必须坚持下去 C ++ 11

这个解决方案看起来很干净,我可以添加模板参数和断言 对于函数中的每个参数。这将扩展相当不错,没有 代码重复。唯一的缺点可能是编译时间更长(已经是 相当长,但不是很重要)和不太有用的错误消息 (希望用const Spinor覆盖。)

使用static_assert调用时的错误消息如下:GCC:

int **

在评论中指出使用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

这是更短的,也许更多的是succint。错误消息不包含 不过,我的手写信息了。至少不会发生错误 函数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变种更好。

1 个答案:

答案 0 :(得分:2)

GCC和clang就在这里,英特尔C ++是错误的。

标准的相关部分是资格转换 [conv.qual](部分编号可能是4.4或4.5)。 C ++ 11和C ++ 1z之间的措辞已经改变(成为C ++ 17)...但是在所有版本中都允许从最浅层开始在多个级别添加const的代码(C ++ 03,11,14,1z)。

值得注意的是,多级const规则现在适用于指针数组,以前它只适用于多个指针。但实际上我们正在处理一个多指针,因为在参数声明中找到的数组语法具有指针语义。

但是,尝试

可能是值得的
void copy_spinor(Spinor **out, const Spinor *const *in)

如果编译器在应用规则之前混淆/未能将数组类型调整为函数参数列表中的指针类型。