Tengo una struct definida en una biblioteca histórica con, lamentablemente, una desafortunada elección de tipo: unsigned char * en lugar de char* .
struct MyStruct { unsigned char * myMember; }; Esta struct es utilizada por una gran cantidad de aplicaciones C y, cada vez más, por aplicaciones C++. Esas aplicaciones C++ generan un error cuando se usan con strlen , por ejemplo, obligándonos a emitir. Muchos moldes.
Me gustaría remediar eso.
Pero una cosa es extremadamente importante: debe ser absolutamente compatible sin modificaciones y no tener impacto en los proyectos existentes.
Pensé en hacer un union . Por desgracia, mi variable no puede tener el mismo nombre.
struct MyStruct { union { unsigned char * myMember; // char * myMember; Obvioulsy, rejected by the compiler. };Me temo que no hay ninguna solución obvia. ¿Me equivoco?
No puedo cambiar el tamaño de la estructura porque está asignada en una memoria compartida de tamaño inalterable.
Dado que su objetivo es crear una isla de perfección y no arreglar el mundo, podría crear un adaptador con un constructor implícito a partir de esta estructura anterior y usar ese adaptador en el código de "isla de perfección".
struct BetterMyStruct { char* myMember; BetterMyStruct(MyStruct const& x): myMember((char*)x.myMember) { // any ugly secret hacks you want or need } }; ... size_t better_strlen(BetterMyStruct const& x) { // perfect function return std::string_view(x.myMember).size(); } ... better_strlen(MyStruct(...)); // perfect function invocation on imperfect inputsHay un remedio más simple. A menudo, cuando está molesto por tener que hacer algo demasiadas veces, simplemente puede resolverlo haciendo eso una vez dentro de una función:
std::size_t unsigned_strlen(const unsigned char* str) noexcept { return std::strlen(reinterpret_cast<const char*>(str)); }Dado que puede cambiar MyStruct , le sugiero que simplemente elimine unsigned , lo que rompería el ABI existente y requeriría la recompilación de todo lo que usa la estructura.
Si esa no es una opción por algún motivo, puede heredar la estructura original y proporcionar un método getter para las aplicaciones de C++.
// use this in C apps struct MyStruct { unsigned char* myMember; }; // use this in C++ apps struct MyStructCpp : MyStruct { MyStructCpp(const MyStruct& rhs) : MyStruct(rhs) {} char* myMemberAsChar() const { return reinterpret_cast<char*>(myMember); } };Nota: No puede resolver esto en C++, porque a C estricto tampoco le gustan los unsigned char* . Incluso con la configuración laxa del compilador de C, obtendría (ejemplo con la configuración predeterminada de gcc):
los objetivos del puntero en la asignación difieren en el signo [-Wpointer-sign]|
incluso cuando se hace algo básico como una asignación simple ms.myMember="hello"; .
Aquí hay una solución C11.
struct MyStruct { union // anonymous union { unsigned char * myMember; char* myCharMember; }; }; #define myMember myCharMemberCódigo de prueba:
struct MyStruct ms; _Generic(ms.myMember, char*: puts("I'm a char*"), unsigned char*: puts("I'm an unsigned char*")); Sin el #define y te dirá "I'm an unsigned char*" , pero con el #define , "I'm a char*" .
No hay forma de que myMember sea un char* o cualquier otro tipo sin potencialmente romper los proyectos existentes, por la simple razón de que su tipo puede deducirse
void foo(decltype(MyStruct::myMember));Esto causará problemas de ABI, el más leve de los cuales es un error del enlazador.
La medida media es proporcionar una abreviatura para el elenco.
struct MyStruct { #ifdef __cplusplus char* signed_myMember() { return (char*)myMember; } #endif }; Afortunadamente, char puede crear alias de cualquier cosa en C++, lo que lo hace legal.