【问题标题】:Getting compile-time constant offsetof of base class in multiple-inheritance在多重继承中获取基类的编译时常量偏移量
【发布时间】:2017-09-20 16:34:19
【问题描述】:

看这个例子:

struct s77 {
    char d[77];
};

struct s1 {
    char d;
};

struct Foo: s77, s1 {
};

struct Off {
    static const int v = std::size_t(static_cast<s1*>(static_cast<Foo*>(nullptr)+1)) - std::size_t(static_cast<Foo*>(nullptr)+1);
};

此代码尝试将s1Foo 中的偏移量放入Off::v。此代码使用 GCC/clang 编译(没有任何警告),但无法使用 VS2015/VS2017 编译(错误 C2131:表达式未计算为常量)

哪个编译器是正确的?

我能否以符合标准的方式实现此功能?如果不可能,是否可以创建一个适用于 VS2015/VS2017 的工作解决方案?我愿意接受任何可行的解决方案,即使根据标准具有未定义的行为(但恰好适用于 VS2015 和 VS2017)。 Off::v 必须是编译时间常数。


我最初的问题是:我有一个自己的tuple 实现,它是通过多重继承实现的(如clang 的tuple)。我想为元组创建一个编译时常量“描述符”,其中包含其所有成员在元组中的偏移量。该描述符也包含每个元组成员的函数指针。如果我手动创建这个描述符,它看起来像这样(例如):

struct Entry {
    int offset;
    void (*function)(void *member);
};

Entry descriptor[] = {
    { 0, &SomeType1::static_function },
    { 12, &SomeType2::static_function },
    { 20, &SomeType3::static_function }
};

这样做的目的是我可以有一个通用函数(它不是模板),它可以使用这个描述符在每个元组成员上调用一个特定于类型的函数:

void call(void *tuple, const Entry *entries, int n) {
    for (int i=0; i<n; i++) {
        entries[i].function(static_cast<char *>(tuple)+entries[i].offset);
    }
}

(这个解决方案而不是模板化call函数的原因是call实际上是我真实代码中的一个巨大函数,entry[i].function调用不能从中分解出来。我想避免大量代码重复。)

【问题讨论】:

  • 由于您的类型没有en.cppreference.com/w/cpp/concept/StandardLayoutType,我非常怀疑您是否可以在不进入未定义行为领域的情况下计算任何偏移量。
  • @Jodocus:没问题,正如我在问题中所说,如果代码恰好可以工作,UB 在这种情况下很好。
  • 考虑使用指向成员的指针而不是偏移量。

标签: c++


【解决方案1】:

怎么样:

struct Entry {
    void* (*data_member_getter)(void*);
    void (*function)(void *member);
};

namespace details
{
    template <std::size_t I, typename Tuple>
    constexpr void* voidPGetter(void* tuple)
    {
        return &std::get<I>(*reinterpret_cast<Tuple*>(tuple));
    }

    template <typename Tuple, std::size_t I>
    constexpr MakeEntry()
    {
        using type = std::tuple_element_t<I, Tuple>;
        return { &voidPGetter<I, Tuple>, &type::static_function };
    }

    template <typename Tuple, std::size_t ... Is>
    constexpr std::array<Entry, sizeof...(Is)>
    ComputeEntryHelper(std::index_sequence<Is...>)
    {
        return {{MakeEntry<Is, Tuple>()...}};
    }
}

template <typename Tuple>
constexpt auto ComputeEntry()
{
    constexpr auto size = std::tuple_size<Tuple>::value;
    return details::ComputeEntryHelper(std::make_index_sequence<size>());
}

然后

void call(void* tuple, const Entry* entries, int n) {
    for (int i = 0; i != n; ++i) {
        entries[i].function(entries[i].data_member_getter(tuple));
    }
}

所以不是偏移,而是有一个函数来获取数据。

【讨论】:

  • 谢谢,有趣的解决方案!但我想继续使用偏移量而不是函数调用(因为函数调用的开销 - 是的,我知道它并不大,但是这个 call 函数使用了很多,所以它需要尽可能高效)。 (我描述我最初的问题的原因是人们可以看到我为什么想要编译时偏移。)
猜你喜欢
  • 1970-01-01
  • 2016-08-24
  • 1970-01-01
  • 2022-01-03
  • 2021-11-13
  • 2017-07-17
  • 1970-01-01
  • 2021-04-25
  • 1970-01-01
相关资源
最近更新 更多