字符串结构与字符串数组相同吗?

时间:2020-12-20 01:07:30

标签: c++ arrays struct casting

我正在处理的代码具有多个结构,仅由 string 字段组成。例如:

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

    string Nth;
};

这些目前都从数据库加载并存储在不同的 map<string, somestruct> 中——每个都有单独的方法。方法是相同的(除了 SQL 查询和字段名称)——每个查询的第一个字段成为地图的索引,随后的字段构成结构的字段:

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));
    }
}

我想将这些方法合二为一,能够初始化具有不同字段数量和不同字段名称的结构。

这个新方法将接收要运行的 SQL 查询和(空)map。该方法将知道查询返回的字段总数,但它无法通过名称引用单个字段——只能通过序列号。

如果可以安全地将上述结构转换为 string[N],那就可以了。他们可以吗?

(不,我不想“正式地”从 struct 更改为数组,因为字段名称在整个应用程序的其余部分实际使用的地方是有意义的。)

3 个答案:

答案 0 :(得分:3)

您可以像这样引用命名成员的数组:

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';
}

输出:

a b c

答案 1 :(得分:1)

像下面这样的东西在技术上是 UB,但在常见的实现下应该 work,大多数情况下它不应该在编译时被 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]);
}

答案 2 :(得分:1)

我强烈建议不要所有铸造恶作剧!事实上,我实际上并不相信有一种符合标准的方法来获取带有命名字段的 struct,您也可以将其转换为数组。但问题是,在您的问题中,您看起来并不需要 将结构转换为数组。你可以用模板做的很好。

// 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>());
}

您将字段数传递给 Loadmake_index_sequence 将其扩展为索引列表 0, 1, ..., NFields-1,然后 detail::Load 通过 record{row[1], row[2], ..., row[NFields]} 构造记录.假设 record 确实看起来像您在问题中所写的那样,没有构造函数,那么它们被视为聚合,而 {expr1, expr2, ..., exprn} 语法只是按顺序初始化字段。您必须记住每次调用 Load 时的字段数,这可能很糟糕。我认为有一些可怕的模板魔术可以用来自动检测字段的数量,但是如果这是一个问题并且您不想这样做,我只会将字段的数量写入每个类:

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

并将 NFields = T::n_fields 设置为 template 中的默认值。

在 C++11 中,您可以定义 index_sequencemake_index_sequence,即使它们不在标准库中

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;
    };
}
相关问题