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

Mar*_*ing 7 gcc icc c++11

问题

扩展现有守则

我有一个数字库,设计时考虑到了一种"味道".现在我想概括一下.基本数据结构是"旋转器",它本身是一个多维矩阵.有许多函数可以采用这些旋转器的数组.广义函数需要为每种味道采用一个这样的旋转器阵列.

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

void copy_spinor(Spinor *out, const Spinor *in) {
    std::cout << out << " " << in << "\n";
}
Run Code Online (Sandbox Code Playgroud)

我现在的概括是这样的:

void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {
    std::cout << "Fwd: ";
    copy_spinor(out[0], in[0]);
}
Run Code Online (Sandbox Code Playgroud)

在实际代码中,有一个循环覆盖所有num_flav,但这不是真正需要这个演示.

据我所知,必须读取这个const Spinor *(in[num_flav]),因此in是一个指向一个可能num_flav元素数组的指针(或者是另一个数量因为foo[]只是*foo在一个函数参数中),类型指针指向const-spinor.

问题是它在使用a时没有编译Spinor *non_const[2] (没有const),请参阅我之前的问题.从答案那里我已经知道这不能编译,因为在函数内 copy_spinor,指针non_const[0]可以指向某个 const数组Spinor *.然后non_const会指向const数据.因此,这不起作用.

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

void copy_spinor(Spinor *out[num_flav], const Spinor *const in[num_flav]) {}
Run Code Online (Sandbox Code Playgroud)

当我现在将my 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);
}
Run Code Online (Sandbox Code Playgroud)

在海湾合作委员会,它显然解决了第二次copy_spinor超载.s1_a承担前一个角色的变量non_const被允许作为参数.

英特尔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})'\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) {}
           ^
Run Code Online (Sandbox Code Playgroud)

错误消息不是特别有用,因为它没有说明不允许转换.似乎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]);
}
Run Code Online (Sandbox Code Playgroud)

错误是一样的.

自由功能

非成员非朋友非模板函数也会出现同样的问题:

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]);
}
Run Code Online (Sandbox Code Playgroud)

错误消息是一样的:

$ 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) {
       ^
Run Code Online (Sandbox Code Playgroud)

解决方案尝试

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

什么是前进的好方法?我可以随心所欲地改变我的新功能,但我希望尽可能避免触及调用者代码.

Const Wrappers

当我s1_amain函数的定义更改为具有两个时 const,它编译:

const Spinor *const s1_a[1] = {s1};
Run Code Online (Sandbox Code Playgroud)

然后copy_spinor使用正确的参数类型调用函数.

广义代码的每个用户都必须const 为每个函数调用编写那些包装器.这将变得非常混乱.

删除Const正确性.

我可以const从函数参数参数中删除最左边的.这在两个编译器上都可以完全编译.但是,我确实想要记录我没有更改该数组中的任何内容,因此它的值应该是const.

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

#ifdef __INTEL_COMPILER
#define ICPC_CONST
#else
#define ICPC_CONST const
#endif
Run Code Online (Sandbox Code Playgroud)

也许用户将一些spinor定义为const.然后我陷入困境,需要将const放在那里:

const Spinor *s3_a[1] = {s3};

s.copy_spinor(s2_a, s3_a);
Run Code Online (Sandbox Code Playgroud)

添加一个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
Run Code Online (Sandbox Code Playgroud)

这很好用,只有一些重复的代码.由于我添加的函数只是重用现有函数,因此代码重复并不.仍然违反DRY原则.

另一个缺点是过载的数量是2^N其中N是a的参数的数量const *.有些函数最多需要三个参数,因此我需要八个副本.

让模板推导出常数

使用模板可以抽象const SpinorSpinor离开.我可以将函数编写为模板,这样S可以是数据类型.使用a 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]);
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我想指定S只能是一个Spinorconst 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]'
Run Code Online (Sandbox Code Playgroud)

在评论中指出要使用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]);
}
Run Code Online (Sandbox Code Playgroud)

这个更短,也许更多的是succint.但是,错误消息不再包含我的手写消息.至少错误不会发生 的功能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>'
Run Code Online (Sandbox Code Playgroud)

enable_if解决方案看起来比static_assert变体更好.

Ben*_*igt 2

GCC 和 clang 在这里是对的,Intel 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)
Run Code Online (Sandbox Code Playgroud)

以防编译器在应用规则之前感到困惑/无法将函数参数列表中的数组类型调整为指针类型。