Czysty kod do printf size_t w C ++ (lub: Najbliższy odpowiednik% z C99 w C ++)

96

Mam kod w C ++, który wyświetla size_t:

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

Chciałbym, aby kompilacja przebiegła bez ostrzeżeń na architekturach 32- i 64-bitowych.

Gdyby to był C99, mógłbym użyć printf("%z", a);. Ale AFAICT %znie istnieje w żadnym standardowym dialekcie C ++. Więc zamiast tego muszę zrobić

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

co jest naprawdę brzydkie.

Jeśli nie ma możliwości drukowania size_ts wbudowanych w język, zastanawiam się, czy można napisać opakowanie printf lub coś takiego, które wstawi odpowiednie rzutowania na size_ts, aby wyeliminować fałszywe ostrzeżenia kompilatora, jednocześnie zachowując dobre.

Jakieś pomysły?


Edytuj Aby wyjaśnić, dlaczego używam printf: Mam stosunkowo dużą bazę kodu, którą czyszczę. Używa opakowań printf do wykonywania takich czynności, jak „napisz ostrzeżenie, zaloguj się do pliku i prawdopodobnie zamknie kod z błędem”. Być może uda mi się zebrać wystarczającą liczbę C ++ - foo, aby to zrobić za pomocą opakowania cout, ale wolałbym nie zmieniać każdego wywołania warn () w programie tylko po to, aby pozbyć się niektórych ostrzeżeń kompilatora.

Justin L.
źródło
4
Dlaczego w ogóle używasz printf, powinno być pytanie.
Ed S.
czy twój kompilator sprawdza dla ciebie łańcuch i typ printf?
Pod
Mój kompilator rzeczywiście sprawdza ciąg formatu printf i wpisuje check to dla mnie. Chciałbym, aby ta funkcja była włączona.
Justin L.,
2
% zu, z jest specyfikatorem szerokości, a nie specyfikatorem typu. Działa dla c printf, z którego można bezproblemowo korzystać w C ++. Skomentowałem to poniżej, więc zagłosuj na to;)
Will
Jeśli używasz programu Visual Studio, czy nie możesz po prostu użyć "%l"? Czy to nie zawsze będzie odpowiedni rozmiar? A może przenośność ma znaczenie?
Mooing Duck,

Odpowiedzi:

61

Większość kompilatorów ma własne specyfikatory size_ti ptrdiff_targumenty, na przykład Visual C ++ używa odpowiednio% Iu i% Id, myślę, że gcc pozwoli ci użyć% zu i% zd.

Możesz stworzyć makro:

#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

Stosowanie:

size_t a;
printf(JL_SIZE_T_SPECIFIER, a);
printf("The size of a is " JL_SIZE_T_SPECIFIER " bytes", a);
dalle
źródło
5
To nie jest takie proste. To, czy %zjest obsługiwana, czy nie, zależy od środowiska wykonawczego, a nie od kompilatora. Używanie __GNUC__jest więc pewnym problemem, jeśli zmieszasz GCC / mingw z msvcrt (i bez użycia rozszerzonego printf mingw).
jørgensen
68

Specyfikator printfformatu %zubędzie działał dobrze w systemach C ++; nie ma potrzeby, aby było to bardziej skomplikowane.

Będzie
źródło
9
@ChrisMarkle Szybki test pokazuje, że nie działa w MinGW. Witryna MS również go nie wymienia ( msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx ). Przypuszczam, że odpowiedź brzmi: nie.
wump
17

C ++ 11

C ++ 11 importuje C99, więc std::printfpowinien obsługiwać specyfikator %zuformatu C99 .

C ++ 98

Na większości platform size_ti uintptr_tsą równoważne, w takim przypadku możesz użyć PRIuPTRmakra zdefiniowanego w <cinttypes>:

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

Jeśli naprawdę chcesz być bezpieczny, przesyłaj uintmax_ti używaj PRIuMAX:

printf("If the answer is %" PRIuMAX " then what is the question?\n", static_cast<uintmax_t>(a));
Oktalist
źródło
16

W systemie Windows i implementacji programu Visual Studio printf

 %Iu

pracuje dla mnie. zobacz msdn

meissnersd
źródło
Dzięki. Działa VS 2008też. Należy także pamiętać, że można użyć %Id, %Ixi %IXzbyt.
c00000fd
11

Skoro używasz C ++, dlaczego nie skorzystać z IOStreams? Powinno to skompilować się bez ostrzeżeń i działać poprawnie ze świadomością typu, o ile nie używasz martwej dla mózgu implementacji C ++, która nie definiuje operator <<for size_t.

Kiedy trzeba wykonać rzeczywiste dane wyjściowe printf(), nadal można je połączyć z IOStreams, aby uzyskać zachowanie bezpieczne dla typów:

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

Nie jest to super wydajne, ale powyższy przypadek dotyczy operacji we / wy pliku, więc to jest wąskie gardło, a nie ten kod formatujący ciąg.

Warren Young
źródło
Wiem, że Google zabrania używania cout w swoim kodzie. Być może Justin L. pracuje pod takim ograniczeniem.
W moim przypadku (patrz edycja powyżej) ciekawym pomysłem może być próba zaimplementowania funkcji warn () w zakresie cout. Ale wymagałoby to ręcznej analizy ciągów formatujących, co jest ... trudne. :)
Justin L.
Twoja ostatnia edycja jest właściwie przeciwieństwem tego, co myślę, że może mi się przydać. Nie chcę przepisać całego kodu, który wywołuje opakowanie printf, ale nie miałbym nic przeciwko przepisaniu implementacji opakowania printf tak, aby używał cout. Ale nie sądzę, żeby to się stało. :)
Justin L.,
Używaj std::stringstreamzamiast strumieni we / wy.
Thomas Eding
1
Strumienie mają niezręczną notację. Porównaj: printf("x=%i, y=%i;\n", x, y);vs cout << "x=" << x << ", y=" << y << ";" << std::endl;.
Wonder.mice
7

oto możliwe rozwiązanie, ale nie całkiem ładne ...

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
źródło
2
Cóż ... to osiąga mój cel bezpieczeństwa i żadnych ostrzeżeń. Ale ... tak. Zniosę ostrzeżenia, jeśli muszę to zrobić. :)
Justin L.,
1
Nie ładna ?! Zależy od gustu. Pozwala na w pełni przenośne używanie printf z bestiami takimi jak uintptr_t i tym podobne. Wspaniały!
Slava
@ user877329 możesz zbudować ten ciąg formatu jako std :: string, a następnie dołączyć GetPrintfID <size_t> :: id w miejscu, w którym go potrzebujesz
stijn
@stijn Innymi słowy: brak możliwości konkatenacji w czasie kompilacji
user877329
@ user877329 no (chyba że używam makr lub czegoś mi brakuje). Ale dlaczego miałby to być trudny wymóg?
stijn
4

Biblioteki FMT zapewnia szybki przenośny (i bezpieczne) wdrażanie printftym zmodyfikator dla size_t:

#include "fmt/printf.h"

size_t a = 42;

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

Oprócz tego obsługuje składnię ciągów formatu podobną do Pythona i przechwytuje informacje o typie, dzięki czemu nie musisz podawać ich ręcznie:

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

Został przetestowany z głównymi kompilatorami i zapewnia spójne wyniki na różnych platformach.

Zastrzeżenie : jestem autorem tej biblioteki.

vitaut
źródło
3

Efektywny typ będący podstawą size_t zależy od implementacji . C Standard definiuje go jako typ zwracany przez operator sizeof; Oprócz tego, że jest bez znaku i jest rodzajem typu całkowitego, size_t może być prawie wszystkim, którego rozmiar może pomieścić największą wartość, której oczekuje się od sizeof ().

W związku z tym ciąg formatu, który ma być używany dla parametru size_t, może się różnić w zależności od serwera. Powinien zawsze mieć „u”, ale może to być l lub d, a może coś innego ...

Sztuczka może polegać na rzutowaniu go na największy typ całkowity na maszynie, zapewniając brak strat w konwersji, a następnie użycie ciągu formatu związanego z tym znanym typem.

mjv
źródło
Byłoby fajnie rzutując moje size_ts na największy typ całkowity na maszynie i używając ciągu formatu związanego z tym typem. Moje pytanie brzmi: czy istnieje sposób, w jaki mogę to zrobić, zachowując czysty kod (ostrzeżenia tylko o prawidłowych błędach w ciągu formatu printf, bez brzydkich rzutów itp.)? Mógłbym napisać opakowanie, które zmienia ciąg formatu, ale wtedy GCC nie byłby w stanie dać mi ostrzeżeń, kiedy legalnie zepsułem swój ciąg formatu.
Justin L.
Użyj makr CPP, aby przetestować rozmiar typów; przejdź do tego, który pasuje i określ ciąg formatu, który pasuje do pasującego typu.
Wyraźniej
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());
    };
};

Później w kodzie:

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

Red.Wave
źródło