Limpe o código para printf size_t em C ++ (ou: equivalente mais próximo de% z de C99 em C ++)

96

Eu tenho algum código C ++ que imprime um size_t:

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

Eu gostaria que isso fosse compilado sem avisos nas arquiteturas de 32 e 64 bits.

Se fosse C99, eu poderia usar printf("%z", a);. Mas AFAICT %znão existe em nenhum dialeto C ++ padrão. Então, em vez disso, eu tenho que fazer

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

o que é realmente feio.

Se não houver nenhum recurso para imprimir size_ts embutidos na linguagem, eu me pergunto se é possível escrever um wrapper printf ou algum tal que insira os casts apropriados nos size_ts de modo a eliminar avisos do compilador espúrios enquanto mantém os bons.

Alguma ideia?


Editar Para esclarecer por que estou usando printf: Tenho uma base de código relativamente grande que estou limpando. Ele usa printf wrappers para fazer coisas como "escrever um aviso, registrá-lo em um arquivo e, possivelmente, sair do código com um erro". Posso conseguir reunir C ++ - foo suficiente para fazer isso com um invólucro cout, mas prefiro não alterar todas as chamadas warn () no programa apenas para me livrar de alguns avisos do compilador.

Justin L.
fonte
4
Por que você está usando printf deve ser a questão.
Ed S.
seu compilador inspeciona a string printf e verifica o tipo para você?
Pod
Meu compilador realmente inspeciona a string de formato printf e digita check it for me. Eu gostaria de manter esse recurso ativado.
Justin L.
2
% zu, z é um especificador de largura, não um especificador de tipo. Funciona para o c printf que você pode usar perfeitamente a partir do C ++. Eu comentei sobre isso abaixo, então vote a favor;)
Será
Se você estiver usando o Visual Studio, não pode simplesmente usar "%l"? Não será sempre do tamanho certo? Ou a portabilidade é importante?
Mooing Duck

Respostas:

61

A maioria dos compiladores tem seu próprio especificador para size_te ptrdiff_targumentos, Visual C ++, por exemplo, usa% Iu e% Id respectivamente. Acho que o gcc permitirá que você use% zu e% zd.

Você pode criar uma 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
fonte
5
Não é tão fácil. Se %zhá suporte ou não, depende do tempo de execução, não do compilador. Usar __GNUC__é, portanto, um pouco problemático, se você misturar GCC / mingw com msvcrt (e sem usar o printf aumentado de mingw).
jørgensen
68

O printfespecificador de formato %zufuncionará bem em sistemas C ++; não há necessidade de torná-lo mais complicado.

Vai
fonte
9
@ChrisMarkle Um teste rápido mostra que não funciona no MinGW. Além disso, o site da MS não o lista ( msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx ). Suponho que a resposta seja não.
wump
17

C ++ 11

C ++ 11 importa C99, portanto, std::printfdeve oferecer suporte ao %zuespecificador de formato C99 .

C ++ 98

Na maioria das plataformas, size_te uintptr_tsão equivalentes, caso em que você pode usar a PRIuPTRmacro definida em <cinttypes>:

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

Se você realmente deseja estar seguro, transmita uintmax_te use PRIuMAX:

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

No Windows e na implementação do Visual Studio de printf

 %Iu

funciona para mim. ver msdn

meissnersd
fonte
Obrigado. Funciona VS 2008também. Também tenha em mente que se pode usar %Id, %Ixe %IXmuito.
c00000fd
11

Já que você está usando C ++, por que não usar IOStreams? Isso deve compilar sem avisos e fazer a coisa certa ciente do tipo, contanto que você não esteja usando uma implementação C ++ com morte cerebral que não defina um operator <<para size_t.

Quando a saída real tiver que ser feita printf(), você ainda pode combiná-la com IOStreams para obter um comportamento de tipo seguro:

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

Não é supereficiente, mas seu caso acima lida com E / S de arquivo, então esse é o seu gargalo, não este código de formatação de string.

Warren Young
fonte
Eu sei que o Google proíbe o uso de cout em seu código. Talvez Justin L. esteja trabalhando sob tal restrição.
No meu caso (veja a edição acima), uma ideia interessante pode ser tentar implementar a função warn () em termos de cout. Mas isso envolveria analisar strings de formato manualmente, o que é ... complicado. :)
Justin L.
Sua última edição é na verdade o oposto do que acho que pode funcionar para mim. Não quero reescrever todo o código que invoca um wrapper printf, mas não me importaria de reescrever a implementação do wrapper printf para usar cout. Mas não acho que isso vá acontecer. :)
Justin L.
Use em std::stringstreamvez de fluxos IO.
Thomas Eding
1
Streams tem notação estranha. Compare: printf("x=%i, y=%i;\n", x, y);vs cout << "x=" << x << ", y=" << y << ";" << std::endl;.
wonder.mice
7

aqui está uma solução possível, mas não é muito 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
fonte
2
Bem ... isso atinge meu objetivo de segurança e sem avisos. Mas ... yeesh. Aceitarei os avisos se for o que tenho que fazer. :)
Justin L.
1
Feio?! Depende do gosto. Ele permite o uso totalmente portátil de printf com feras como uintptr_t e similares. Ótimo!
Slava
@ user877329 você pode construir essa string de formato como um std :: string e, em seguida, acrescentar GetPrintfID <size_t> :: id no lugar que você precisa
stijn
@stijn Em outras palavras: nenhuma concatenação de tempo de compilação disponível
user877329
@ user877329 não (a menos que esteja usando macros ou esteja faltando alguma coisa). Mas por que isso seria um requisito difícil?
stijn
4

A biblioteca fmt fornece uma implementação portátil rápida (e segura) deprintf incluir o zmodificador para size_t:

#include "fmt/printf.h"

size_t a = 42;

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

Além disso, ele oferece suporte à sintaxe de string de formato semelhante ao Python e captura informações de tipo para que você não precise fornecê-las manualmente:

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

Ele foi testado com os principais compiladores e fornece saída consistente em todas as plataformas.

Isenção de responsabilidade : sou o autor desta biblioteca.

vitaut
fonte
3

O tipo efetivo subjacente size_t é dependente da implementação . C Standard o define como o tipo retornado pelo operador sizeof; além de não ter sinal e ser uma espécie de tipo integral, o size_t pode ser praticamente qualquer coisa cujo tamanho pode acomodar o maior valor esperado para ser retornado por sizeof ().

Conseqüentemente, a string de formato a ser usada para size_t pode variar dependendo do servidor. Deve sempre ter o "u", mas pode ser l ou d ou talvez outra coisa ...

Um truque poderia ser convertê-lo no maior tipo integral da máquina, garantindo que não houvesse perda na conversão e, em seguida, usar a string de formato associada a esse tipo conhecido.

mjv
fonte
Eu seria legal lançar meus size_ts para o maior tipo integral na máquina e usar a string de formato associada a esse tipo. Minha pergunta é: Existe uma maneira de fazer isso mantendo um código limpo (avisos apenas para erros de string de formato printf legítimo, sem conversão feia, etc.)? Eu poderia escrever um wrapper que mudasse a string de formato, mas o GCC não seria capaz de me dar avisos quando eu realmente baguncei minha string de formato.
Justin L.
Use macros CPP para testar o tamanho dos tipos; vá para aquele que corresponde e especifique a string de formato que vai com o tipo de correspondência.
Mais 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());
    };
};

Posteriormente no código:

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

Red.Wave
fonte