Есть ли способ получить доступ к n-му элементу структуры?

#c #struct #types

#c #структура #типы

Вопрос:

У меня есть структура указателей, где указатели имеют произвольный тип, но известны во время компиляции.

 struct ptrs
{
    int* a;
    const char** b;
    int* c;
    float* d;
};
 

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

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

Вся кодовая база находится на c 20, поэтому мне не нужно беспокоиться о поддержке C.

Редактировать: я хочу это для следующего использования. Я создам переменный макрос для преобразования произвольного количества входных данных в массив указателей на then . Это будет передано функции для обработки, которая будет обрабатывать их в произвольном порядке в соответствии с командной строкой. Например, «return $var1 $ var2». Для обработки требуется произвольный доступ к элементам один или несколько раз, следовательно, «индексирование» структуры. Однако для поддержки арифметики тип должен быть известен.

Комментарии:

1. Какова конечная цель этого упражнения? Чего вы на самом деле пытаетесь достичь? Ваш вопрос звучит как проблема XY ; сам по себе он не имеет особого смысла.

2. C не поддерживает отражение.

3. Хотите верьте, хотите нет, на самом деле это проще гарантировать с помощью C.

4. В какой форме вы ожидаете получить тип, если n известно только во время выполнения, как компилятор может дать вам это?

5. std::tuple Может помочь использование (непосредственно через преобразование).

Ответ №1:

Да, это можно сделать. Часть кода немного сложно написать, но Boost PFR уже проделал тяжелую работу, поэтому вы могли бы сделать что-то вроде:

 ptrs p;

float * f = boost::pfr::get<3>(p); // get the `float *` member
*f = 123.4f;                       // and write via the pointer
 

[Обратите внимание, что я разбил это только на две строки, чтобы я мог комментировать каждый шаг — что-то вроде *boost::pfr::get<3>(p) = 123.4f; должно быть хорошо.]

Для всех, кому не все равно, основная идея того, как вы это делаете, довольно проста — это только детали, которые становятся сложными.

Основной подход заключается в использовании структурированной привязки для получения интересующего вас элемента. Сложность возникает из нескольких источников: вы не знаете, с какими типами вы имеете дело, и типы различаются, поэтому это не так просто, как создать массив элементов и индексировать в массив.

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

 template <std::size_t index>
constexpr autoamp; get(autoamp; your_struct)
{
    autoamp; [zero, one, two, three] = your_struct;

    if constexpr (index == 0)
        return zero;
    if constexpr (index == 1)
        return one;
    if constexpr (index == 2)
        return two;
    return three;
}
 

Я повторю: Я сильно упрощаю (например, я только что использовал фиксированное количество членов), но это, по крайней мере, примерно общая идея — используйте структурированную привязку для захвата членов и условие constexpr, чтобы выбрать, какой из них вам нужен, с параметрами шаблона (в форме auto ) для существенновсе части, так что вы можете иметь дело с любыми типами, которые там есть.

О, есть еще одна деталь, с которой немного сложно разобраться: в нынешнем виде эта реализация может работать только для одного типа. То есть, если вы попытаетесь использовать get<0> и get<3> , вы получите сообщение об ошибке о несоответствии в выведенном типе возвращаемого значения. Итак, в его нынешнем виде вы можете использовать get<0> or get<2> (или что угодно), но не оба, так что это чисто ограниченное доказательство концепции, а не практическая реализация (вообще).

Хотя вот рабочая демонстрационная программа:

 #include <cstddef>
#include <iostream>

struct ptrs {
    int* a;
    const char** b;
    int* c;
    float* d;
};

template <std::size_t index>
constexpr autoamp; get(autoamp; your_struct)
{
    autoamp; [zero, one, two, three] = your_struct;

    if constexpr (index == 0)
        return zero;
    if constexpr (index == 1)
        return one;
    if constexpr (index == 2)
        return two;
    return three;
}

int main() {
    ptrs p;
    p.d = new float; // `get<3>(p) = new float;` works fine too.

    *get<3>(p) = 123.4;
    
    // show we're accessing the same data either way:
    std::cout << *get<3>(p) << "t" << *(p.d) << "n";

    // type information is retained. For example, if we try to do this:
    // get<3>(p) = new int;  // <-- note int instead of float
    // g   10 gives the error: "cannot convert ‘int*’ to ‘float*’ in assignment"
}
 

Комментарии:

1. хотя это интересно, я не уверен, как этот подход был отмечен как ответ, поскольку он требует знания времени компиляции n . вопрос, как выяснено, требует доступа к n, определяемому строковым вводом во время выполнения

Ответ №2:

Это единственный способ, без создания множества механизмов метаданных.

 void* get(ptrs constamp; data, int n)
{
    // TODO: add checks
    return ((void**)amp;data)[n];
}

 

Следует отметить, что стандарт не гарантирует, что все указатели данных имеют одинаковый размер. Однако на большинстве архитектур они есть. Поэтому обратитесь к документации вашего компилятора или используйте a static_assert для подтверждения.

Комментарии:

1. Это UB типа «Вероятно, работает». Не забывайте следить за скрытыми метаданными в более сложных структурах данных.