如何在编译时确定元组元素的偏移量?

How to determine the offset of an element of a tuple at compile time?

我需要在编译时确定元组的某个索引元素的偏移量。

我试过这个函数,复制自(接近尾声),

template <std::size_t I, typename Tuple>
constexpr std::ptrdiff_t element_offset() {
    Tuple p;
    return 
          (char*)(&std::get<I>(*static_cast<Tuple *>(&p)))
        - (char*)(static_cast<Tuple*>(&p))
    ;
}

包括我删除 p 并将 &p 替换为 nullptr 的变体。

这个函数似乎在运行时运行良好,但我无法在编译时评估它。

https://godbolt.org/z/MzGxfT1cc

int main() {
    using Tuple = std::tuple<int, double, int, char, short, long double>;
    constexpr std::size_t index = 3;
    constexpr std::ptrdiff_t offset = element_offset<index, Tuple>();  // ERROR HERE, cannot evaluate constexpr context

    Tuple t;
    assert(( reinterpret_cast<char*>(&t) + offset == reinterpret_cast<char*>(&std::get<index>(t))  ));  // OK, when compiles (without "constexpr" offset)
}

我理解这可能是因为 reinterpret_casts 无法在编译时完成。 但到目前为止,它基本上是唯一被证明有效的函数(在运行时)。

有没有办法以可以在编译类型上评估的方式重写这个函数?

我也在 的开头尝试了这些接近的列表,但它们都给出了垃圾结果(至少在 GCC 中),因为它们假定元组元素的特定顺序并且偏移量是通过“ walking" 按索引索引并对齐字节。

你可以使用这个:

template <std::size_t I, typename Tuple>
constexpr std::size_t element_offset() {
    using element_t = std::tuple_element_t<I, Tuple>;
    static_assert(!std::is_reference_v<element_t>);
    union {
        char a[sizeof(Tuple)];
        Tuple t{};
    };
    auto* p = std::addressof(std::get<I>(t));
    t.~Tuple();
    std::size_t off = 0;
    for (std::size_t i = 0;; ++i) {
        if (static_cast<void*>(a + i) == p) return i;
    }
}

这避免了必须 reinterpret_cast 到 char 指针,并且不应该有任何未定义的行为。

您还可以通过不初始化元组来使用不能在常量表达式中默认构造的元组:

template <std::size_t I, typename Tuple>
constexpr std::size_t element_offset() {
    using element_t = std::tuple_element_t<I, Tuple>;
    static_assert(!std::is_reference_v<element_t>);
    union u {
        constexpr u() : a{} {}  // GCC bug needs a constructor definition
        char a[sizeof(Tuple)]{};
        Tuple t;
    } x;
    auto* p = std::addressof(std::get<I>(x.t));
    std::size_t off = 0;
    for (std::size_t i = 0;; ++i) {
        if (static_cast<void*>(x.a + i) == p) return i;
    }
}

虽然这在今天的 gcc、clang 和 msvc 中有效,但将来可能不会。