c++11 - Intel C++ cannot convert `T **` to `T const * const *`, GCC can -


the problem

extension of existing code

i have numerical library has been designed 1 “flavor” in mind. want generalize this. basic data structure “spinor” multi dimensional matrix. there lots of functions take arrays of these spinors. generalized functions need take 1 such spinor array each flavor.

say there function which, minimally, following:

void copy_spinor(spinor *out, const spinor *in) {     std::cout << out << " " << in << "\n"; } 

my generalization this:

void copy_spinor(spinor *out[num_flav], const spinor *const in[num_flav]) {     std::cout << "fwd: ";     copy_spinor(out[0], in[0]); } 

in real code, there loop on num_flav, not needed demonstration here.

as far understand, 1 has read const spinor *(in[num_flav]), in pointer array of num_flav elements (or quantity because foo[] *foo in function parameter) of type pointer-to-const-spinor.

the problem not compile when using spinor *non_const[2] (without const), see my earlier question. answer there have learned must not compile because within function copy_spinor, pointer non_const[0] made point const array of spinor *. non_const point const data. therefore not work.

my conclusion adding another const make correct:

void copy_spinor(spinor *out[num_flav], const spinor *const in[num_flav]) {} 

when pass non_const second parameter, function cannot change in[0] anything, because pointer immutable. has served me gcc 6.3. going production intel c++ 17, not work more.

the minimal working example following:

#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); } 

on gcc, apparently resolves second copy_spinor overload. variable s1_a takes role of previous non_const, allowed argument.

problems intel c++ 17

intel c++ 17 however, not accept that:

$ 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 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: candidate rejected because arguments not match       void copy_spinor(spinor *out[num_flav], const spinor *const in[num_flav]) {}            ^ const-spinor-const.cpp(10): note: candidate rejected because arguments not match       void copy_spinor(spinor *out, const spinor *in) {}            ^ 

the error message not particularly helpful because not conversion not allowed. seems const problem.

is there perhaps miss intel c++? lacking feature or did make use of unofficial gcc extension? bug in intel c++ or gcc?

update: example compiles current clang.

non-template class

the same issue persists when class solver not template class. since t a[2] same t a[2] , t *a in function argument, can write function this, not needing uint8_t num_flav:

void copy_spinor(spinor *out[], const spinor *const in[]) {     std::cout << "fwd: ";     copy_spinor(out[0], in[0]); } 

the error same.

free functions

the same issue happens non-member non-friend non-template functions:

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]); } 

the error message same:

$ icpc -wall -pedantic const-spinor-const.cpp  --std=c++11 const-spinor-const.cpp(97): error: no instance of overloaded function "free_spinor" matches argument list             argument types are: (spinor *[1], spinor *[1])       free_spinor(s2_a, s1_a);       ^ const-spinor-const.cpp(30): note: candidate rejected because arguments not match   void free_spinor(spinor *out[], const spinor *const in[]) {        ^ const-spinor-const.cpp(26): note: candidate rejected because arguments not match   void free_spinor(spinor *out, const spinor *in) {        ^ 

solution attempts

in order run code in production, see following options. none of them particularly attractive.

what way go forward? can alter new functions want, avoid touching caller code as possible.

const wrappers

when change definition of s1_a in main function have 2 const, compiles:

const spinor *const s1_a[1] = {s1}; 

then function copy_spinor gets called correct argument type.

every user of generalized code has write const wrappers every single function call. messy.

remove const correctness.

i can remove left-most const function argument parameters. compiles cleanly on both compilers. however, want document not changing in array, therefore values should const.

a partial solution use preprocessor constant removes const intel compiler only.

#ifdef __intel_compiler #define icpc_const #else #define icpc_const const #endif 

perhaps user has defined spinor const. stuck , need have const there in place:

const spinor *s3_a[1] = {s3};  s.copy_spinor(s2_a, s3_a); 

it should easier add const remove it, solution pretty lacking. upstream author reject changes due changes in code.

add non-const overload each function

adding overload each function possible. have 2 variants of generalized function, second 1 gets enabled when work intel compiler:

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 

that works well, there duplication of code. since added functions re-use existing functions, not that much code duplication. still violation of dry principle.

another disadvantage number of overloads 2^n n number of parameters const *. there functions taking 3 arguments this, therefore need 8 copies.

let template deduce constness

abstracting const spinor , spinor away possible templates. write function template such s can either data type. using static_assert give more informative error message.

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 `const spinor` or `spinor`.");      std::cout << "fwd: ";     copy_spinor(out[0], in[0]); } 

ideally, specify s can spinor or const spinor. perhaps possible c++14 , beyond, have stick c++11.

this solution looks pretty clean, can add template argument , assert each argument in function. scale pretty , there no code duplication. downside longer compilation time (already rather long, not important) , less useful error messages (hopefully covered static_assert).

the error message when calling int ** following 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 here const-spinor-const.cpp:40:9: error: static assertion failed: template parameter must `const spinor` or `spinor`.          static_assert(std::is_same<spinor, s>::value ||          ^~~~~~~~~~~~~ const-spinor-const.cpp:45:20: error: no matching function call '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 argument 2 'int* const' '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 argument 1 'float (*)[3][4][2][8]' 'float (**)[3][4][2][8]' 

in comments pointed out use enable_if. that, function looks following:

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]); } 

this shorter, , perhaps more succint. error message not contain hand-written message more , though. @ least error not occur within function copy_spinor @ calling site, user knows went wrong. perhaps better. , enable_if explains itself, @ least more experienced template users.

const-spinor-const.cpp: in function 'int main(int, char**)': const-spinor-const.cpp:86:37: error: no matching function call '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 here const-spinor-const.cpp:51:5: error: no type named 'type' in 'struct std::enable_if<false, void>' 

the enable_if solution looks better static_assert variant.

gcc , clang right here, intel c++ wrong.

the relevant portion of standard qualification conversions [conv.qual] (the section number may either 4.4 or 4.5). wording has changed between c++11 , c++1z (to become c++17)... code adds const @ multiple levels starting shallowest, allowed in versions (c++03, 11, 14, 1z).

one change of note multi-level const rule applies arrays of pointers, formerly applied multiple pointers. dealing multiple pointer, because array syntax, when found in parameter declaration, has pointer semantics.

still, might worthwhile try

void copy_spinor(spinor **out, const spinor *const *in) 

in case compiler got confused / failed adjust array types pointer types within function parameter list before applying rule.


Comments

Popular posts from this blog

javascript - Clear button on addentry page doesn't work -

c# - Selenium Authentication Popup preventing driver close or quit -

tensorflow when input_data MNIST_data , zlib.error: Error -3 while decompressing: invalid block type -