Чистый код для printf size_t в C ++ (или: Ближайший эквивалент% z из C99 в C ++)

96

У меня есть код C ++, который печатает size_t:

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

Я бы хотел, чтобы это скомпилировалось без предупреждений как на 32-, так и на 64-битных архитектурах.

Если бы это был C99, я бы мог использовать printf("%z", a);. Но AFAICT %zне существует ни на одном стандартном диалекте C ++. Так что вместо этого я должен сделать

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

что действительно некрасиво.

Если в языке нет size_tвстроенных средств для печати s, мне интересно, можно ли написать оболочку printf или что-то подобное, которое будет вставлять соответствующие приведения в size_ts, чтобы устранить ложные предупреждения компилятора, сохраняя при этом хорошие.

Любые идеи?


Изменить Чтобы прояснить, почему я использую printf: у меня относительно большая база кода, которую я очищаю. Он использует оболочки printf, чтобы делать такие вещи, как «написать предупреждение, записать его в файл и, возможно, выйти из кода с ошибкой». Я мог бы собрать достаточно C ++ - foo, чтобы сделать это с помощью оболочки cout, но я бы предпочел не изменять каждый вызов warn () в программе, просто чтобы избавиться от некоторых предупреждений компилятора.

Джастин Л.
источник
4
Почему вы вообще используете printf, должен возникнуть вопрос.
Эд С.
Ваш компилятор проверяет строку printf и проверяет тип?
Pod
Мой компилятор действительно проверяет строку формата printf и проверяет ее для меня. Я бы хотел, чтобы эта функция была включена.
Джастин Л.
2
% zu, z - спецификатор ширины, а не спецификатор типа. Он работает для c printf, который вы можете легко использовать с C ++. Я прокомментировал это ниже, так что голосуйте за него;)
Will
Если вы используете Visual Studio, разве вы не можете просто использовать "%l"? Разве это не всегда будет правильный размер? Или имеет значение мобильность?
Mooing Duck

Ответы:

61

У большинства компиляторов есть свои собственные спецификаторы size_tи ptrdiff_tаргументы, например, Visual C ++ использует% Iu и% Id соответственно, я думаю, что gcc позволит вам использовать% zu и% zd.

Вы можете создать макрос:

#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

Использование:

size_t a;
printf(JL_SIZE_T_SPECIFIER, a);
printf("The size of a is " JL_SIZE_T_SPECIFIER " bytes", a);
Далле
источник
5
Это не так просто. Если %zподдерживается или нет , зависит от среды выполнения, а не компиляции. Используя __GNUC__поэтому немного проблемы, если смешать GCC / MinGW с MSVCRT (и без использования дополненного Printf MinGW в).
jørgensen
68

Спецификатор printfформата %zuотлично работает в системах C ++; нет необходимости усложнять его.

Будет
источник
9
@ChrisMarkle Быстрый тест показывает мне, что это не работает в MinGW. Также на сайте MS это не указано ( msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx ). Я полагаю, ответ отрицательный.
wump
17

C ++ 11

C ++ 11 импортирует C99, поэтому std::printfдолжен поддерживать %zuспецификатор формата C99 .

C ++ 98

На большинстве платформ size_tи uintptr_tэквивалентны, и в этом случае вы можете использовать PRIuPTRмакрос, определенный в <cinttypes>:

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

Если вы действительно хотите быть в безопасности, uintmax_tпримените и используйте PRIuMAX:

printf("If the answer is %" PRIuMAX " then what is the question?\n", static_cast<uintmax_t>(a));
Окталист
источник
16

В windows и реализация printf в Visual Studio

 %Iu

работает для меня. см. msdn

Meissnersd
источник
Спасибо. Тоже работает VS 2008. Кроме того, имейте в виду , что можно использовать %Id, %Ixи %IXтоже.
c00000fd
11

Поскольку вы используете C ++, почему бы не использовать IOStreams? Это должно компилироваться без предупреждений и делать правильные вещи с учетом типов, если вы не используете безумную реализацию C ++, которая не определяет operator <<for size_t.

Когда фактический вывод должен быть выполнен с помощью printf(), вы все равно можете объединить его с IOStreams, чтобы получить безопасное поведение:

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

Это не суперэффективно, но ваш случай, приведенный выше, касается файлового ввода-вывода, так что это ваше узкое место, а не этот код форматирования строки.

Уоррен Янг
источник
Я знаю, что Google запрещает использование cout в своем коде. Возможно, Джастин Л. работает с таким ограничением.
В моем случае (см. Правку выше) интересной идеей может быть попытка реализовать функцию warn () в терминах cout. Но это потребует синтаксического анализа строк формата вручную, что ... сложно. :)
Джастин Л.
Твоя последняя редакция на самом деле противоположна тому, что, по моему мнению, могло бы сработать для меня. Я не хочу переписывать весь код, который вызывает оболочку printf, но я бы не возражал переписать реализацию оболочки printf для использования cout. Но я не думаю, что это произойдет. :)
Джастин Л.
Используйте std::stringstreamвместо потоков ввода-вывода.
Thomas Eding
1
У потоков неудобные обозначения. Для сравнения: printf("x=%i, y=%i;\n", x, y);против cout << "x=" << x << ", y=" << y << ";" << std::endl;.
wonder.mice
7

вот возможное решение, но оно не совсем красивое ..

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
источник
2
Что ж ... это достигает моей цели безопасности и никаких предупреждений. Но ... ага. Я приму предупреждения, если это то, что мне нужно сделать. :)
Джастин Л.
1
Не красиво ?! Зависит от вкуса. Он позволяет полностью переносить printf с такими чудовищами, как uintptr_t и ему подобные. Большой!
Слава
@ user877329, вы можете построить эту строку формата как std :: string, а затем добавить GetPrintfID <size_t> :: id в
нужном
@stijn Другими словами: конкатенация во время компиляции
недоступна
@ user877329 нет (если не использую макросы, иначе мне что-то не хватает). Но почему это должно быть жестким требованием?
stijn 08
4

Библиотека fmt обеспечивает быструю переносимую (и безопасную) реализацию, printfвключающую zмодификатор для size_t:

#include "fmt/printf.h"

size_t a = 42;

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

В дополнение к этому он поддерживает синтаксис строки формата, подобный Python, и фиксирует информацию о типе, поэтому вам не нужно вводить ее вручную:

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

Он был протестирован с основными компиляторами и обеспечивает согласованный вывод на всех платформах.

Отказ от ответственности : я являюсь автором этой библиотеки.

витаут
источник
3

Эффективный тип, лежащий в основе size_t, зависит от реализации . Стандарт C определяет его как тип, возвращаемый оператором sizeof; Помимо беззнакового и своего рода интегрального типа, size_t может быть практически любым, размер которого может вместить наибольшее значение, которое, как ожидается, будет возвращено функцией sizeof ().

Следовательно, строка формата, которая будет использоваться для size_t, может различаться в зависимости от сервера. На нем всегда должна быть буква «u», но это может быть l или d или что-то еще ...

Уловкой может быть приведение его к самому большому целочисленному типу на машине, гарантируя отсутствие потерь при преобразовании, а затем использование строки формата, связанной с этим известным типом.

mjv
источник
Было бы здорово преобразовать мои size_ts в самый большой интегральный тип на машине и использовать строку формата, связанную с этим типом. Мой вопрос: есть ли способ сделать это, сохранив чистый код (предупреждения только для допустимых ошибок строки формата printf, без уродливых приведений и т. Д.)? Я мог бы написать оболочку, которая изменяет строку формата, но тогда GCC не сможет выдавать мне предупреждения, когда я законно испортил строку формата.
Джастин Л.
Используйте макросы CPP для проверки размера типов; выберите тот, который соответствует, и укажите строку формата, которая соответствует типу соответствия.
Четче
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());
    };
};

Позже в коде:

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

Красная волна
источник