Código limpio para printf size_t en C ++ (o: equivalente más cercano de% z de C99 en C ++)

96

Tengo un código C ++ que imprime un size_t:

size_t a;
printf("%lu", a);

Me gustaría que esto se compilara sin advertencias en arquitecturas de 32 y 64 bits.

Si esto fuera C99, podría usar printf("%z", a);. Pero AFAICT %zno existe en ningún dialecto estándar de C ++. Entonces, en cambio, tengo que hacer

printf("%lu", (unsigned long) a);

que es realmente feo.

Si no hay ninguna facilidad para imprimir size_tmensajes de correo electrónico integrados en el lenguaje, me pregunto si es posible escribir un contenedor printf o algo así que inserte las conversiones apropiadas en size_tlos mensajes de correo electrónico para eliminar las advertencias falsas del compilador mientras se mantienen las buenas.

¿Algunas ideas?


Editar Para aclarar por qué estoy usando printf: tengo una base de código relativamente grande que estoy limpiando. Usa envoltorios printf para hacer cosas como "escribir una advertencia, registrarla en un archivo y posiblemente salir del código con un error". Podría reunir suficiente C ++ - foo para hacer esto con un contenedor cout, pero prefiero no cambiar todas las llamadas warn () en el programa solo para deshacerme de algunas advertencias del compilador.

Justin L.
fuente
4
¿Por qué estás usando printf? Debería ser la pregunta.
Ed S.
¿Su compilador inspecciona la cadena printf y comprueba el tipo por usted?
Pod
De hecho, mi compilador inspecciona la cadena de formato printf y escribe, compruébalo por mí. Me gustaría mantener esta función activada.
Justin L.
2
% zu, z es un especificador de ancho, no de tipo. Funciona para c printf que puede usar sin problemas desde C ++. Lo he comentado a continuación, así que vote por él;)
Will
Si está usando Visual Studio, ¿no puede simplemente usarlo "%l"? ¿No será siempre del tamaño correcto? ¿O importa la portabilidad?
Mooing Duck

Respuestas:

61

La mayoría de los compiladores tienen su propio especificador size_ty ptrdiff_targumentos, Visual C ++, por ejemplo, usa% Iu y% Id respectivamente, creo que gcc le permitirá usar% zu y% zd.

Podrías crear una macro:

#if defined(_MSC_VER) || defined(__MINGW32__) //__MINGW32__ should goes before __GNUC__
  #define JL_SIZE_T_SPECIFIER    "%Iu"
  #define JL_SSIZE_T_SPECIFIER   "%Id"
  #define JL_PTRDIFF_T_SPECIFIER "%Id"
#elif defined(__GNUC__)
  #define JL_SIZE_T_SPECIFIER    "%zu"
  #define JL_SSIZE_T_SPECIFIER   "%zd"
  #define JL_PTRDIFF_T_SPECIFIER "%zd"
#else
  // TODO figure out which to use.
  #if NUMBITS == 32
    #define JL_SIZE_T_SPECIFIER    something_unsigned
    #define JL_SSIZE_T_SPECIFIER   something_signed
    #define JL_PTRDIFF_T_SPECIFIER something_signed
  #else
    #define JL_SIZE_T_SPECIFIER    something_bigger_unsigned
    #define JL_SSIZE_T_SPECIFIER   something_bigger_signed
    #define JL_PTRDIFF_T_SPECIFIER something-bigger_signed
  #endif
#endif

Uso:

size_t a;
printf(JL_SIZE_T_SPECIFIER, a);
printf("The size of a is " JL_SIZE_T_SPECIFIER " bytes", a);
dalle
fuente
5
No es tan fácil. Si %zes compatible o no, depende del tiempo de ejecución, no del compilador. __GNUC__Por lo tanto, usar es un poco problemático si mezcla GCC / mingw con msvcrt (y sin usar la printf aumentada de mingw).
jørgensen
68

El printfespecificador de formato %zufuncionará bien en sistemas C ++; no hay necesidad de hacerlo más complicado.

Será
fuente
9
@ChrisMarkle Una prueba rápida me muestra que no funciona en MinGW. Además, el sitio de MS no lo incluye ( msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx ). Supongo que la respuesta es no.
wump
17

C ++ 11

C ++ 11 importa C99, por lo que std::printfdebería admitir el %zuespecificador de formato C99 .

C ++ 98

En la mayoría de las plataformas, size_ty uintptr_tson equivalentes, en cuyo caso puede utilizar la PRIuPTRmacro definida en <cinttypes>:

size_t a = 42;
printf("If the answer is %" PRIuPTR " then what is the question?\n", a);

Si realmente quieres estar seguro, envía uintmax_ty usa PRIuMAX:

printf("If the answer is %" PRIuMAX " then what is the question?\n", static_cast<uintmax_t>(a));
Oktalista
fuente
16

En windows y la implementación de printf de Visual Studio

 %Iu

funciona para mi. ver msdn

meissnersd
fuente
Gracias. Funciona VS 2008también. También tenga en cuenta que se puede usar %Id, %Ixy %IXtambién.
c00000fd
11

Ya que está usando C ++, ¿por qué no usar IOStreams? Eso debería compilarse sin advertencias y hacer lo correcto con reconocimiento de tipos, siempre que no esté utilizando una implementación de C ++ con muerte cerebral que no defina un operator <<for size_t.

Cuando la salida real se tiene que hacer con printf(), todavía se puede combinar con iostreams para conseguir un comportamiento de tipo seguro:

size_t foo = bar;
ostringstream os;
os << foo;
printf("%s", os.str().c_str());

No es súper eficiente, pero su caso anterior trata con E / S de archivos, por lo que ese es su cuello de botella, no este código de formato de cadena.

Warren Young
fuente
Sé que Google prohíbe el uso de cout en su código. Quizás Justin L. esté trabajando bajo tal restricción.
En mi caso (ver editar arriba), una idea interesante podría ser intentar implementar la función warn () en términos de cout. Pero eso implicaría analizar las cadenas de formato manualmente, lo cual es ... complicado. :)
Justin L.
Tu última edición es en realidad lo opuesto a lo que creo que podría funcionar para mí. No quiero reescribir todo el código que invoca una envoltura printf, pero no me importaría reescribir la implementación de la envoltura printf para usar cout. Pero no creo que eso vaya a suceder. :)
Justin L.
Úselo en std::stringstreamlugar de transmisiones IO.
Thomas Eding
1
Las secuencias tienen una notación incómoda. Compare: printf("x=%i, y=%i;\n", x, y);vs cout << "x=" << x << ", y=" << y << ";" << std::endl;.
wonder.mice
7

aquí hay una posible solución, pero no es muy bonita ...

template< class T >
struct GetPrintfID
{
  static const char* id;
};

template< class T >
const char* GetPrintfID< T >::id = "%u";


template<>
struct GetPrintfID< unsigned long long > //or whatever the 64bit unsigned is called..
{
  static const char* id;
};

const char* GetPrintfID< unsigned long long >::id = "%lu";

//should be repeated for any type size_t can ever have


printf( GetPrintfID< size_t >::id, sizeof( x ) );
stijn
fuente
2
Bueno ... eso logra mi objetivo de seguridad y sin advertencias. Pero ... sí. Aceptaré las advertencias si eso es lo que tengo que hacer. :)
Justin L.
1
¡¿No es bonito?! Depende del gusto. Permite el uso completamente portátil de printf con bestias como uintptr_t y similares. ¡Excelente!
Slava
@ user877329, puede crear esa cadena de formato como std :: string y luego agregar GetPrintfID <size_t> :: id en el lugar que lo necesite
stijn
@stijn En otras palabras: no hay concatenación en tiempo de compilación disponible
user877329
@ user877329 no (a menos que use macros o me falte algo). Pero, ¿por qué sería un requisito difícil?
stijn
4

La biblioteca fmt proporciona una implementación portátil rápida (y segura) deprintf incluir el zmodificador para size_t:

#include "fmt/printf.h"

size_t a = 42;

int main() {
  fmt::printf("%zu", a);
}

Además de eso, es compatible con la sintaxis de cadenas de formato similar a Python y captura información de tipo para que no tenga que proporcionarla manualmente:

fmt::print("{}", a);

Ha sido probado con los principales compiladores y proporciona resultados consistentes en todas las plataformas.

Descargo de responsabilidad : soy el autor de esta biblioteca.

vitaut
fuente
3

El tipo efectivo subyacente a size_t depende de la implementación . C Standard lo define como el tipo devuelto por el operador sizeof; aparte de no estar firmado y ser una especie de tipo integral, size_t puede ser prácticamente cualquier tamaño cuyo tamaño pueda acomodar el valor más grande que se espera que devuelva sizeof ().

En consecuencia, la cadena de formato que se utilizará para size_t puede variar según el servidor. Siempre debe tener la "u", pero puede ser l o d o tal vez algo más ...

Un truco podría ser convertirlo en el tipo integral más grande en la máquina, asegurando que no se pierdan en la conversión y luego usar la cadena de formato asociada con este tipo conocido.

mjv
fuente
Sería genial lanzar mis size_ts al tipo integral más grande en la máquina y usar la cadena de formato asociada con este tipo. Mi pregunta es: ¿Hay alguna manera de hacer esto mientras mantengo el código limpio (advertencias solo para errores legítimos de cadena de formato de printf, sin cambios desagradables, etc.)? Podría escribir un contenedor que cambie la cadena de formato, pero luego GCC no podría darme advertencias cuando legítimamente estropeé mi cadena de formato.
Justin L.
Utilice macros CPP para probar el tamaño de los tipos; elija el que coincida y especifique la cadena de formato que corresponde al tipo coincidente.
Más claro
0

#include <cstdio>
#include <string>
#include <type_traits>

namespace my{
    template<typename ty>
    auto get_string(ty&& arg){
        using rty=typename::std::decay_t<::std::add_const_t<ty>>;
        if constexpr(::std::is_same_v<char, rty>)
            return ::std::string{1,arg};
        else if constexpr(::std::is_same_v<bool, rty>)
            return ::std::string(arg?"true":"false");
        else if constexpr(::std::is_same_v<char const*, rty>)
            return ::std::string{arg};
        else if constexpr(::std::is_same_v<::std::string, rty>)
            return ::std::forward<ty&&>(arg);
        else
            return ::std::to_string(arg);
    };

    template<typename T1, typename ... Args>
    auto printf(T1&& a1, Args&&...arg){
        auto str{(get_string(a1)+ ... + get_string(arg))};
        return ::std::printf(str.c_str());
    };
};

Más adelante en el código:

my::printf("test ", 1, '\t', 2.0);

Red.Wave
fuente