元组类 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 子句中具有备用路径,以确保编译器递归是合理限制的。
我正在使用模板元编程创建类似元组的 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 子句中具有备用路径,以确保编译器递归是合理限制的。