元组类 class 模板的反向内存布局

Reverse memory layout for tuple-like class templates

我正在使用模板元编程创建类似元组的 class 模板。与 std::tuple 不同,我的元组中的所有值都是同一类型,并且值的数量等于模板参数列表中列出的 "channel constants" 的数量。

这是一个例子:

enum class Foo { Foo1, Foo2, Foo3, Foo4 };

MyTuple<float, Foo, Foo::Foo1, Foo::Foo2, Foo::Foo3> good(1.0f, 2.0f, 3.0f);

这实例化了一个变量 good,它包含三个浮点数,这些浮点数与作用域枚举中的相应常量相关联。按照一些通用元组的教程,我想出了一个几乎可以满足我所有需求的实现。它的本质是:

template <typename Rep, typename Space, Space... Channels>
struct MyTuple {};

template <typename Rep, typename Space, Space First, Space... Rest>
struct MyTuple<Rep, Space, First, Rest...> : public MyTuple<Rep, Space, Rest...> {
    using MyBase = MyTuple<Rep, Space, Rest...>;

    template <typename Head, typename... Tail>
    constexpr MyTuple(Head value, Tail... tail) noexcept :
        MyBase(tail...), m_value(value) {}

    Rep m_value;
};

唯一的缺点是,如果我按内存顺序遍历值,它们将以相反的顺序返回。例如,下面的代码列出 3 2 1 而不是 1 2 3.

auto *p = reinterpret_cast<float *>(&good);
std::cout << p[0] << ' ' << p[1] << ' ' << p[2] << '\n';

这似乎是递归构建元组的自然结果。 有没有办法获得所有相同的行为,但值的内存顺序向前而不是相反?

为什么不把存储做成一个数组呢?

template <typename Rep, typename Space, Space... Channels>
struct MyTuple<Rep, Space, Channels...>
{
    std::array<Rep, sizeof...(Channels)> m_values;
};

根据 John Zwinck 的建议,这是我想出的解决方案。

template <typename Rep, typename Space, Space... Channels>
class MyTuple2 {
    public:
        static constexpr auto ChannelCount = sizeof...(Channels);
        using Values = std::array<Rep, ChannelCount>;
        MyTuple2(Values values) : m_values(std::move(values)) {}

        constexpr Rep operator[](Space channel) const noexcept {
            return find<0>(channel);
        }

    private:
        static constexpr std::array<Space, ChannelCount> IDs = { Channels... };

        template <std::size_t N>
        constexpr Rep find(Space channel) const noexcept {
            if constexpr (N == ChannelCount) {
                return find_failed();
            } else {
                return (channel == IDs[N]) ? m_values[N] : find<N+1>(channel);
            }
        }

        Rep find_failed() const {
            throw std::domain_error("Requested channel isn't in MyTuple");
        }

        Values m_values;
};

对象实例仅包含实际值,按顺序存储在 std::array 中。我将频道 ID 保留在 class 静态 std::array 中,这可能不是绝对必要的,但对我的使用来说很简单且可以接受,甚至在将来可能会有用。我的元组包含的通道数量非常少,通常为 1 到 3 个,从不超过 32 个,因此递归限制很好,编译器似乎在优化它方面做得很好。

模板元编程中递归的终止条件通常是一种特殊化,但很难以人类可读和 clang、gcc 和 MSVC 可接受的方式应用到这里。请注意,if 语句必须是 constexpr 并且在显式 else 子句中具有备用路径,以确保编译器递归是合理限制的。