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
Post a Comment