简体   繁体   中英

Is struct of strings the same as array of strings?

I'm dealing with code, that has multiple structures consisting of string -fields only. For example:

struct record {
    string first;
    string second;
    string third;

    string Nth;
};

These are currently all loaded from a database and stored in different map<string, somestruct> -- with a separate method for each. The methods are the same (except for the SQL-queries and field-names) -- the first field of each query becomes the map's index, and the subsequent ones form the structure's fields:

void
LoadFoo()
{
    rows = RunSQLQuery("SELECT key, first, second FROM sometable");
    for (auto row = rows.cbegin(); row != rows.cend(); row++) {
        struct record r = {
            first = row[1];
            second = row[2];
        };
        Foos.insert(make_pair(row[0], r));
    }
}

I'd like to unify these methods into one, capable of initializing structures with different number of fields -- and differing field-names.

This new method will receive the SQL-query to run and the (empty) map . The method will know the total number of fields the query returns, but it would not be able to refer to individual fields by name -- only by sequence number.

Which would be Ok, if the above structures can be safely cast into string[N] . Can they?

(No, I don't want to change from struct s to arrays "officially", because the field-names are meaningful where they are actually used throughout the rest of the application.)

You can have references to an array for the named members like this:

struct record {
    std::string items[3];
    std::string& first = items[0];
    std::string& second = items[1];
    std::string& third = items[2];
};

int main()
{
    record r{"a", "b", "c"};

    std::cout << r.first << ' ' << r.second << ' ' << r.third << '\n';
}

Output:

a b c

Something like the following is technically UB, but should work under common implementations, and most cases where it doesn't should be caught at compile time by static_assert .

#include <string>

struct List { std::string a0, a1; };
struct Array { std::string a[2]; };

union Overlay {
     List ll;
     Array aa;

     Overlay() : aa() { }
     ~Overlay() { aa.~Array(); }
};

static_assert(sizeof(List) == sizeof(Array));
static_assert(offsetof(List, a0) == offsetof(Array, a[0]));
static_assert(offsetof(List, a1) == offsetof(Array, a[1]));

void foo() {
    Overlay la;    
static_assert(&la.ll.a0 == &la.aa.a[0]);
static_assert(&la.ll.a1 == &la.aa.a[1]);
}

I strongly advise against all casting shenanigans, In fact, I don't actually believe there's a standard-compliant way to get struct with named fields that you can also cast into an array. But the thing is, in your problem, it doesn't look like you need the structures to be cast to arrays. You can made do just fine with a template.

// this definition should not be directly referenced but needs to be included in every file that indirectly uses it anyway
// convention is to stick it in some namespace where no one will touch it
namespace detail {
    template<typename T, std::size_t... Ns>
    void Load(std::map<std::string, T> &sink, char const *query, std::index_sequence<Ns...>) {
        for(auto const &row : RunSQLQuery(query)) sink.insert(std::make_pair(row[0], T{row[Ns + 1]...}));
    }
}
template<typename T, std::size_t NFields>
void Load(std::map<std::string, T> &sink, char const *query) {
    detail::Load(sink, query, std::make_index_sequence<NFields>());
}

You pass the number of fields to Load , make_index_sequence expands that into the list of indices 0, 1, ..., NFields-1 , and then detail::Load constructs the records by record{row[1], row[2], ..., row[NFields]} . Assuming the record s really do look as you've written in the question, with no constructors, then they are considered aggregates, and the {expr1, expr2, ..., exprn} syntax simply initializes the fields in order. You do have to remember the number of fields at each call to Load , which may be bad. I think there's some horrible template magic you can do to autodetect the number of fields, but if it's a concern and you don't want to do that, I would just write the number of fields into each class:

struct record {
    std::string first, second, third;
    static constexpr std::size_t n_fields = 3;
};

and set NFields = T::n_fields as the default in the template .

In C++11, you can define index_sequence and make_index_sequence even though they are absent from the standard library

template<std::size_t...>
struct index_sequence { };
namespace detail {
    template<std::size_t>
    struct make_index_sequence_impl;
}
template<std::size_t N>
using make_index_sequence = typename detail::make_index_sequence_impl<N>::type;
namespace detail {
    template<>
    struct make_index_sequence_impl<0> {
        using type = index_sequence<>;
    };
    template<std::size_t N>
    struct make_index_sequence_impl {
        template<typename>
        struct helper;
        template<std::size_t... Ns>
        struct helper<index_sequence<Ns...>> {
            using type = index_sequence<Ns..., N - 1>;
        };
        using type = typename helper<make_index_sequence<N - 1>>::type;
    };
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM