英特尔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 SpinorSpinor抽象出来。 我可以将函数编写为模板,以便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只能是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]'

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