У меня есть некоторые структуры, которые могут содержать определенное количество целых чисел без знака (это либо uint32_t, либо uint64_t). Я хочу распечатать значение этих структур в понятной форме. Например, у меня может быть структура, как показано ниже.
struct test {
uint32_t ab = 0;
uint64_t cd = 1;
uint32_t ef = 2;
};
Я думаю, что у меня мог бы быть метод, передающий адрес этой структуры и распечатывающий его, используя размер этой структуры. Но не уверен, как написать код.
Я знаю, что каждый член либо uint32_t, либо uint64_t. Будет ли это полезно? Некоторое базовое понимание информации, содержащейся в структуре, было бы для меня приемлемым.
Боюсь, недостаточно. Если они могут появляться в любом порядке и в любом количестве, вы ничего не можете сказать только по адресу структуры и размеру.
С С++ 14 вы можете использовать magic_get.





Как уже упоминалось, 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
};
};
И написанный ранее оператор потока тоже будет работать для него.
@MaxLanghof - Вы имеете в виду как magic_get Полухина? Разве это не было ограничено C++ 14 и выше? Вопрос помечен как C++11.
Ах да, структурированные привязки явно отсутствуют, если мы работаем на C++11. Мне нужно уделять больше внимания языковым тегам.
В каком отношении это лучше, чем просто написать функцию, говорящую std::cout << x.ab << ' ' << x.cd << ' ' x.de;?
@aparpara - Несколько способов. (1) Он работает со всеми стандартными типами потоков, а не только с std::cout. (2) Логика централизована, и нет необходимости писать функцию для типа каждый, которая пытается сохранить тот же формат. Менее подвержены ошибкам копирования и вставки. (3) Эту же таблицу можно использовать в аналогичном operator >> для чтения типа из текстового файла. И, возможно, в других местах, где такая рефлексивная информация может быть полезна.
@StoryTeller, похоже, причины недействительны. Потому что (1) Если вы хотите работать со всеми стандартными типами потоков, вы можете легко сделать поток параметром. (2) Вам все еще нужно написать struct_members для типа каждый. (3) Вопрос про печать, но если хотите полноценную сериализацию, не надо изобретать велосипед, есть boost::serializationboost.org/doc/libs/1_69_0/libs/serialization/doc/…
@aparpara - Да, участники, а не логика. Аналогично тому, как boost::serialization требует собственных хуков. Несмотря на это, ОП уже прокомментировал под своим постом, что хочет некоторая информация об участниках. Этот ответ ориентирован на эту информацию. Но ничего, если вы не одобряете, я не против.
Если вам нужно просто базовое представление о том, что находится внутри, вы можете переинтерпретировать структуру как массив 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*. Хотя вы можете легко получить тот же результат, сделав это таким образом.
@MaxLanghof, строго говоря, ты прав. Но в данном конкретном случае я не вижу, что может быть источником неопределенного поведения. И да, можно напечатать то же самое, что и sizeof(uint32_t)/sizeof(char) символы без знака.
Источником неопределенного поведения является несоблюдение требования «должен» стандарта C++. Боюсь, код с UB или без него не подлежит обсуждению.
Хорошо, отдадим должное стандарту C++ "должен" :)
Я предпочитаю отдавать должное портативным программам, которые не ломаются, когда на них запускаются различные дезинфицирующие средства, или компиляторам, которым предоставлена полная свобода действий для оптимизации. Но конечно, давайте будем саркастичными.
Одним из вариантов было бы использовать 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); }. Таким образом, вы сохраняете имя члена, и у вас нет более крупной структуры.
Даже если у вас есть размер структуры... у вас все равно нет точного типа каждого члена. Чтобы делать то, что вы хотите, вам понадобится (статическое) отражение. В C++ его еще нет, несмотря на то, что над ним ведется работа в будущих версиях стандарта.