Распечатайте структуру с известным размером

У меня есть некоторые структуры, которые могут содержать определенное количество целых чисел без знака (это либо uint32_t, либо uint64_t). Я хочу распечатать значение этих структур в понятной форме. Например, у меня может быть структура, как показано ниже.

struct test {
    uint32_t ab = 0;
    uint64_t cd = 1;
    uint32_t ef = 2;
};

Я думаю, что у меня мог бы быть метод, передающий адрес этой структуры и распечатывающий его, используя размер этой структуры. Но не уверен, как написать код.

Даже если у вас есть размер структуры... у вас все равно нет точного типа каждого члена. Чтобы делать то, что вы хотите, вам понадобится (статическое) отражение. В C++ его еще нет, несмотря на то, что над ним ведется работа в будущих версиях стандарта.

StoryTeller - Unslander Monica 16.04.2019 08:28

Я знаю, что каждый член либо uint32_t, либо uint64_t. Будет ли это полезно? Некоторое базовое понимание информации, содержащейся в структуре, было бы для меня приемлемым.

taylorm 16.04.2019 08:32

Боюсь, недостаточно. Если они могут появляться в любом порядке и в любом количестве, вы ничего не можете сказать только по адресу структуры и размеру.

StoryTeller - Unslander Monica 16.04.2019 08:33

С С++ 14 вы можете использовать magic_get.

Jarod42 16.04.2019 12:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
132
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Как уже упоминалось, C++11 не имеет механизма отражения. Единственный способ получить список участников — создать свой собственный механизм. Поскольку вы упомянули, что каждый элемент всегда имеет один из двух типов, это должно быть довольно прямолинейно. Например, путем создания класса свойств.

template<class T>
struct mem_ptr {
    // Tagged union type to generically refer to a member of a struct T
    enum {u32, u64} alive;
    union {
        uint32_t T::* u32_ptr;
        uint64_t T::* u64_ptr;
    };
    mem_ptr(uint32_t T::* u32_ptr) : alive(u32), u32_ptr(u32_ptr) {}
    mem_ptr(uint64_t T::* u64_ptr) : alive(u64), u64_ptr(u64_ptr) {}
};

template<class> struct struct_members;

template<>
struct struct_members<test> {
    mem_ptr<test> members[3] = {
      &test::ab, &test::cd, &test::ef
    };
};

template<class T>
auto operator<<(std::ostream& os, T const& str) -> decltype(struct_members<T>::members, os) {
    struct_members<T> const mem;
    char const *delim = "";
    os << "{ ";
    for(auto& m : mem.members) {
        os << delim;
        delim = ", ";
        switch(m.alive) {
            case m.u32: os << (str.*(m.u32_ptr)); break;
            case m.u64: os << (str.*(m.u64_ptr)); break;
            default: break;
        }
    }
    os << " }";
    return os;
}

При проверке вышеизложенного (каламбур) на палочка печатается:

{ 0, 1, 2 }

Имея это на месте, вы можете добавить поддержку новой структуры, просто определив для нее таблицу struct_members:

struct test2 {
    uint32_t ab1 = 5;
    uint64_t cd2 = 3;
    uint32_t ef3 = 8;
};

template<>
struct struct_members<test2> {
    mem_ptr<test2> members[3] = {
      &test2::ab1, &test2::cd2, &test2::ef3
    };
};

И написанный ранее оператор потока тоже будет работать для него.

Технически есть способы определить количество членов и "обработать" их с помощью (ab) использования инициализации фигурных скобок и структурированных привязок...
Max Langhof 16.04.2019 09:09

@MaxLanghof - Вы имеете в виду как magic_get Полухина? Разве это не было ограничено C++ 14 и выше? Вопрос помечен как C++11.

StoryTeller - Unslander Monica 16.04.2019 09:10

Ах да, структурированные привязки явно отсутствуют, если мы работаем на C++11. Мне нужно уделять больше внимания языковым тегам.

Max Langhof 16.04.2019 09:11

В каком отношении это лучше, чем просто написать функцию, говорящую std::cout << x.ab << ' ' << x.cd << ' ' x.de;?

aparpara 16.04.2019 09:42

@aparpara - Несколько способов. (1) Он работает со всеми стандартными типами потоков, а не только с std::cout. (2) Логика централизована, и нет необходимости писать функцию для типа каждый, которая пытается сохранить тот же формат. Менее подвержены ошибкам копирования и вставки. (3) Эту же таблицу можно использовать в аналогичном operator >> для чтения типа из текстового файла. И, возможно, в других местах, где такая рефлексивная информация может быть полезна.

StoryTeller - Unslander Monica 16.04.2019 09:48

@StoryTeller, похоже, причины недействительны. Потому что (1) Если вы хотите работать со всеми стандартными типами потоков, вы можете легко сделать поток параметром. (2) Вам все еще нужно написать struct_members для типа каждый. (3) Вопрос про печать, но если хотите полноценную сериализацию, не надо изобретать велосипед, есть boost::serializationboost.org/doc/libs/1_69_0/libs/serialization/doc/…

aparpara 16.04.2019 12:24

@aparpara - Да, участники, а не логика. Аналогично тому, как boost::serialization требует собственных хуков. Несмотря на это, ОП уже прокомментировал под своим постом, что хочет некоторая информация об участниках. Этот ответ ориентирован на эту информацию. Но ничего, если вы не одобряете, я не против.

StoryTeller - Unslander Monica 16.04.2019 12:45

Если вам нужно просто базовое представление о том, что находится внутри, вы можете переинтерпретировать структуру как массив uint32_t-s и вывести их в шестнадцатеричном формате.

#include <iostream>
#include <iomanip>

struct test {
    uint32_t ab = 0;
    uint64_t cd = 1;
    uint32_t ef = 2;
};

// For those who don't respect strict aliasing rules mentioned in comments
/*
template<class T>
void printStruct(const T& s)
{
    auto* b = reinterpret_cast<const uint32_t*>(&s);
    auto* e = b + sizeof(T)/sizeof(uint32_t);
    std::cout << std::hex;
    for (auto* i = b; i != e; ++i)
        std::cout << std::setw(sizeof(uint32_t)*2) << std::setfill('0') << *i << ' ';
    std::cout << std::dec;
}
*/

// For those who do respect strict aliasing rules mentioned in comments
template<class T>
void printStruct(const T& s)
{
    const auto sc = sizeof(char);
    const auto n = sizeof(uint32_t)/sc;
    auto* b = reinterpret_cast<const unsigned char*>(&s);
    auto* e = b + sizeof(T)/(sc*n);

    std::cout << std::hex;
    for (auto* i = b; i != e; i += n)
    {
        for (auto j = 0; j < n; ++j)
            // For a big-endian machine n - 1 - j must be replaced by j
            std::cout << std::setw(sc*2) << std::setfill('0') << *(i + n - 1 - j);
        std::cout << ' ';
    }
    std::cout << std::dec;
}

int main()
{
    printStruct(test());
    return 0;
}

Но подпрограмма также напечатает байты выравнивания, и на машине с прямым порядком байтов две части uint64_t будут перевернуты.

Например. на моей машине печатает

00000000 00000000 00000001 00000000 00000002 00007ffe

Где первый 00000000 — это ab, второй 00000000 — это выравнивание, 00000001 00000000 — это cd, 00000002 — это de и 00007ffe снова выравнивание.

Это неопределенное поведение (нарушение строгого псевдонима). Вам разрешено осматривать объекты через (возможно unsigned) char*, но не через uint32_t*. Хотя вы можете легко получить тот же результат, сделав это таким образом.

Max Langhof 16.04.2019 09:11

@MaxLanghof, строго говоря, ты прав. Но в данном конкретном случае я не вижу, что может быть источником неопределенного поведения. И да, можно напечатать то же самое, что и sizeof(uint32_t)/sizeof(char) символы без знака.

aparpara 16.04.2019 09:20

Источником неопределенного поведения является несоблюдение требования «должен» стандарта C++. Боюсь, код с UB или без него не подлежит обсуждению.

StoryTeller - Unslander Monica 16.04.2019 09:23

Хорошо, отдадим должное стандарту C++ "должен" :)

aparpara 16.04.2019 09:32

Я предпочитаю отдавать должное портативным программам, которые не ломаются, когда на них запускаются различные дезинфицирующие средства, или компиляторам, которым предоставлена ​​полная свобода действий для оптимизации. Но конечно, давайте будем саркастичными.

StoryTeller - Unslander Monica 16.04.2019 09:36

Одним из вариантов было бы использовать std::tuple для ваших структур. например ваша структура может быть определена как:

typedef std::tuple< uint32_t, uint64_t, uint32_t > test;

Затем вы можете распечатать любой кортеж, используя:

template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t)
    {
        TuplePrinter<Tuple, N - 1>::print(t);
        std::cout << ", " << std::get<N - 1>(t);
    }
};

template<class Tuple>
struct TuplePrinter<Tuple, 1> {
    static void print(const Tuple& t)
    {
        std::cout << std::get<0>(t);
    }
};

template<class... Args>
void print(const std::tuple<Args...>& t)
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}

Если вы хотите вернуть своих именованных членов, вы можете получить структуру из своего кортежа, что-то вроде этого:

struct test : public std::tuple< uint32_t, uint64_t, uint32_t >
{
    typedef std::tuple< uint32_t, uint64_t, uint32_t > base;
    test()
    : base( 0, 1, 2 ),
      ab( std::get< 0 >( *this ) ),
      cd( std::get< 1 >( *this ) ),
      ef( std::get< 2 >( *this ) )
    {
    }
    uint32_t& ab;
    uint64_t& cd;
    uint32_t& ef;
};

или альтернативно:

template<typename T>
void print(const T& t)
{
    print(t.tuple);
}

struct test
{
    uint32_t ab = 0;
    uint64_t cd = 1;
    uint32_t ef = 2;
    typedef std::tuple< uint32_t&, uint64_t&, uint32_t& > tuple_t;
    const tuple_t tuple = { ab, cd, ef };
};

или (как предложил @Jarod42):

void print(const test& t)
{
    print(std::tie(t.ab, t.cd, t.ef));
}

Я бы сделал немного иначе, предоставив метод, похожий на auto as_tuple(test t) { return std::tie(t.ab, t.cd, t.ef); }. Таким образом, вы сохраняете имя члена, и у вас нет более крупной структуры.

Jarod42 16.04.2019 12:26

Другие вопросы по теме

Похожие вопросы

Как использовать несколько перегрузок для получения разных ответов в зависимости от количества переданных чисел?
Используйте список инициализаторов для создания вектора с одним элементом
Как вы можете скопировать значение узла из одной структуры данных в другую
Неправильные аргументы для графического конвейера vulkan, несмотря на примеры соответствия подписи и исходный код
В чем разница между "auto x = vector<int>()" и "vector<int> x"?
Как исправить основную функцию, вызывающую неправильную функцию при передаче параметров? (С++)
Почему я не получаю ошибку сегментации, когда переполняю вектор с помощью своего пользовательского распределителя?
Qt, передать данные другому классу
Я получаю сообщение об ошибке: вызов перегруженного ‘max(Cents&, Cents&)’ неоднозначен
Как вы вызываете функцию внутри цикла for другой функции, передавая массив через функцию?