b1skit
b1skit

Reputation: 339

Extracting sections of C++ source code as strings for use at runtime

I want to be able to copy structure definitions into a string at compile time, so I can inject them into text files at runtime.

For example, this struct

struct MyStruct
{
    int a;
    float b;
};

would be stored as a string/char array available for use at runtime, and would also still be available for use in C++ code like any regular struct.

Is this possible? I assume this will involve some macro magic, but my googling hasn't turned up anything. I'm open to any practical solutions.

For context, the goal here is to mirror generic structures defined in C++ code in glsl/hlsl shader files that I'm concatenating/compiling at runtime. My current solution of copy/pasting the definitions between C++ and shaders is not ideal.

Upvotes: 0

Views: 102

Answers (1)

HolyBlackCat
HolyBlackCat

Reputation: 96316

Something like this?

#define STRUCT(name, ...) STRUCT_(name, struct name {__VA_ARGS__};)
#define STRUCT_(name, ...) \
    struct name \
    { \
        __VA_ARGS__ \
        static constexpr const char *text = #__VA_ARGS__; \
    };

Then

STRUCT( A,
    int x;
    float y;
)

expands to

struct A
{
    int x;
    float y;
    static constexpr const char *text = "struct A {int x; float y;};";
};

But I would rather recommend storing types/names of individual variables; the macro syntax would be something like this:

STRUCT( A,
    (x, int)
    (y, float)
)

A full example: run on gcc.godbolt.org

#include <array>
#include <cstddef>
#include <iostream>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>


#define STRUCT(name_, seq_) \
    struct name_ \
    { \
        /* Generate members. */\
        DETAIL_END(DETAIL_STRUCT_LOOP_DECL_A seq_) \
        /* Store struct name. */\
        static constexpr std::string_view _own_name = #name_; \
        /* Generate name array. */\
        static constexpr std::array< \
            std::string_view, \
            DETAIL_END(DETAIL_STRUCT_LOOP_COUNT_A seq_) \
        > \
        _names = \
        { \
            DETAIL_END(DETAIL_STRUCT_LOOP_NAMES_A seq_) \
        }; \
        /* Return the members as tuple. */\
        [[nodiscard]] constexpr auto _tie() \
        { \
            return std::make_tuple(DETAIL_END(DETAIL_STRUCT_LOOP_TUPLE_0 seq_)); \
        } \
        [[nodiscard]] constexpr auto _tie() const \
        { \
            return std::make_tuple(DETAIL_END(DETAIL_STRUCT_LOOP_TUPLE_0 seq_)); \
        } \
    };

#define DETAIL_END(...) DETAIL_END_(__VA_ARGS__)
#define DETAIL_END_(...) __VA_ARGS__##_END

// Must use `std::type_identity_t<T>` or `std::enable_if_t<true, T>`, otherwise using e.g. an array type will produce `T[N] x;`, which is wrong.
#define DETAIL_STRUCT_LOOP_DECL_A(name_, ...) std::type_identity_t<__VA_ARGS__> name_; DETAIL_STRUCT_LOOP_DECL_B
#define DETAIL_STRUCT_LOOP_DECL_B(name_, ...) std::type_identity_t<__VA_ARGS__> name_; DETAIL_STRUCT_LOOP_DECL_A
#define DETAIL_STRUCT_LOOP_DECL_A_END
#define DETAIL_STRUCT_LOOP_DECL_B_END

#define DETAIL_STRUCT_LOOP_COUNT_A(name_, ...) +1 DETAIL_STRUCT_LOOP_COUNT_B
#define DETAIL_STRUCT_LOOP_COUNT_B(name_, ...) +1 DETAIL_STRUCT_LOOP_COUNT_A
#define DETAIL_STRUCT_LOOP_COUNT_A_END
#define DETAIL_STRUCT_LOOP_COUNT_B_END

#define DETAIL_STRUCT_LOOP_NAMES_A(name_, ...) #name_, DETAIL_STRUCT_LOOP_NAMES_B
#define DETAIL_STRUCT_LOOP_NAMES_B(name_, ...) #name_, DETAIL_STRUCT_LOOP_NAMES_A
#define DETAIL_STRUCT_LOOP_NAMES_A_END
#define DETAIL_STRUCT_LOOP_NAMES_B_END

#define DETAIL_STRUCT_LOOP_TUPLE_0(name_, ...)   name_ DETAIL_STRUCT_LOOP_TUPLE_A
#define DETAIL_STRUCT_LOOP_TUPLE_A(name_, ...) , name_ DETAIL_STRUCT_LOOP_TUPLE_B
#define DETAIL_STRUCT_LOOP_TUPLE_B(name_, ...) , name_ DETAIL_STRUCT_LOOP_TUPLE_A
#define DETAIL_STRUCT_LOOP_TUPLE_0_END
#define DETAIL_STRUCT_LOOP_TUPLE_A_END
#define DETAIL_STRUCT_LOOP_TUPLE_B_END


template <typename T>
constexpr std::string_view Name = T::_own_name;

template <typename T>
constexpr std::size_t MemberCount = std::tuple_size_v<decltype(std::declval<T>()._tie())>;

template <typename T, std::size_t I>
using MemberType = std::tuple_element_t<I, decltype(std::declval<T>()._tie())>;

template <typename T>
[[nodiscard]] constexpr std::string_view MemberName(std::size_t i)
{
    return T::_names[i];
}

template <std::size_t I, typename T>
[[nodiscard]] constexpr auto &&Member(T &&object)
{
    return std::get<I>(object._tie());
}

// A constexpr-friendly `for` loop.
template <auto N, typename F>
void constexpr_for(F &&func)
{
    using type = decltype(N);
    [&]<type ...I>(std::integer_sequence<type, I...>)
    {
        (void(func(std::integral_constant<type, I>{})), ...);
    }
    (std::make_integer_sequence<type, N>{});
}

// ----------------

STRUCT( A,
    (x, int)
    (y, float)
)

/* Expands to:
    struct A
    {
        std::type_identity_t<int> x;
        std::type_identity_t<float> y;
        constexpr std::string_view _own_name = "A"; \
        static constexpr std::array<std::string_view, +1+1> _names = {
            "x",
            "y",
        };
        [[nodiscard]] constexpr auto _tie() { return std::make_tuple(x, y); }
        [[nodiscard]] constexpr auto _tie() const { return std::make_tuple(x, y); }
    };
*/

// This can be automated, but GLSL supports only a limited set of types anyway.
template <typename T> struct TypeName {};
template <> struct TypeName<int> {static constexpr auto value = "int";};
template <> struct TypeName<float> {static constexpr auto value = "float";};

int main()
{
    A a{.x = 1, .y = 2.3f};

    std::cout << "name = " << Name<A> << '\n'; // 2;
    std::cout << "num_members = " << MemberCount<A> << '\n'; // 2;
    constexpr_for<MemberCount<A>>([&](auto index)
    {
        constexpr std::size_t i = index.value;
        std::cout << i << '\n';
        std::cout << "    type = " << TypeName<MemberType<A, i>>::value << '\n';
        std::cout << "    name = " << MemberName<A>(i) << '\n';
        std::cout << "    value = " << Member<i>(a) << '\n';
    });
    
    /*
    name = A
    num_members = 2
    0
        type = int
        name = x
        value = 1
    1
        type = float
        name = y
        value = 2.3
    */
}

Upvotes: 1

Related Questions