我有一个数组(任何秩(,我希望有一个索引运算符:
-
允许丢失索引,因此以下是等效的
a(1, 0, 0, 0); a(1, my::missing);
这本身就很简单(参见下面的示例实现(:只需递归地添加
arg * strides[dim]
,直到它命中my::missing
。 -
允许自动预置零,因此以下内容等效于
a(0, 0, 0, 1); a(1);
这也不难(见下面的示例实现(:递归地添加
arg * strides[dim + offset]
。
我无法理解的是:如何将两者结合起来2的实现。让我一开始就错了脚。(我被限制为<=C++14(
";my::失踪"没有自动预挂起的零
enum class my { missing };
template <size_t dim, class S>
inline size_t index_impl(const S&) noexcept
{
return 0;
}
template <size_t dim, class S, class... Args>
inline size_t index_impl(const S& strides, enum my arg, Args... args) noexcept
{
return 0;
}
template <size_t dim, class S, class Arg, class... Args>
inline size_t index_impl(const S& strides, Arg arg, Args... args) noexcept
{
return arg * strides[dim] + index_impl<dim + 1>(strides, args...);
}
template <class S, class Arg, class... Args>
inline size_t index(const S& strides, Arg arg, Args... args)
{
return index_impl<0>(strides, arg, args...);
}
int main()
{
std::vector<size_t> strides = {8, 4, 2 ,1};
std::cout << index(strides, 1, 2, 0, 0) << std::endl;
std::cout << index(strides, 1, 2, my::missing) << std::endl;
}
在没有"0"的情况下前置零的示例实现;my::失踪">
template <size_t dim, class S>
inline size_t index_impl(const S&) noexcept
{
return 0;
}
template <size_t dim, class S, class Arg, class... Args>
inline size_t index_impl(const S& strides, Arg arg, Args... args) noexcept
{
return arg * strides[dim] + index_impl<dim + 1>(strides, args...);
}
template <class S, class Arg, class... Args>
inline size_t index(const S& strides, Arg arg, Args... args)
{
constexpr size_t nargs = sizeof...(Args) + 1;
if (nargs == strides.size())
{
return index_impl<0>(strides, arg, args...);
}
else if (nargs < strides.size())
{
return index_impl<0>(strides.cend() - nargs, arg, args...);
}
return index_impl<0>(strides, arg, args...);
}
int main()
{
std::vector<size_t> strides = {8, 4, 2 ,1};
std::cout << index(strides, 1, 2) << std::endl;
std::cout << index(strides, 0, 0, 1, 2) << std::endl;
}
在这个答案的早期版本中,我没有提供完整的实现,因为有些东西不适合我。
如果这个index
应该计算扁平多维数组的索引,那么您的示例实现是无效的。问题是隐藏的,因为您正在将index
的两个结果与提供的所有索引进行比较,并缩短假设为零填充的版本
遗憾的是,我在Catch2测试的第一个版本中就采用了这种模式。
以下是对扁平多维数组索引的适当测试,其中最后一个索引与扁平索引匹配:
TEST_CASE("index")
{
std::vector<size_t> strides = { 4, 6, 3, 5 };
SECTION("Padding with leading zeros")
{
constexpr auto i0 = 4;
constexpr auto i1 = 2;
constexpr size_t expected = i0 + i1 * 5;
CHECK(index(strides, 0, 0, i1, i0) == expected);
CHECK(index(strides, 0, 0, i1, i0 - 1) == expected - 1); // last index indexes by one
CHECK(index(strides, i1, i0) == expected);
CHECK(index(strides, i1, i0 - 1) == expected - 1);
}
SECTION("Use my::missing to use padding with tailing zeros")
{
constexpr auto i2 = 4;
constexpr auto i3 = 2;
constexpr size_t expected = (i3 * 6 + i2) * 5 * 3;
CHECK(index(strides, i3, i2, 0, 0) == expected);
CHECK(index(strides, i3, i2, my::missing) == expected);
}
}
现在从你的代码开始,通过这些测试,我得到了这个实现:
template <typename T, typename... Ts>
struct last_type_helper {
using type = typename last_type_helper<Ts...>::type;
};
template <typename T>
struct last_type_helper<T> {
using type = T;
};
template <typename... Ts>
using last_type = typename last_type_helper<Ts...>::type;
enum class my { missing };
template <typename... Ts>
constexpr bool LastTypeIsMy = std::is_same<my, last_type<Ts...>>::value;
template <class StrideIter>
size_t index_impl(size_t base, StrideIter)
{
return base;
}
template <class StrideIter>
size_t index_impl(size_t base, StrideIter, my)
{
return base;
}
template <class StrideIter, typename Tn, typename... Ts>
size_t index_impl(size_t base, StrideIter it, Tn xn, Ts... x)
{
return index_impl(base * *it + xn, it + 1, x...);
}
template <class S, class... Args>
size_t index(const S& strides, Args... args)
{
const size_t offset = strides.size() - sizeof...(Args);
const size_t advenceBy = LastTypeIsMy<Args...> ? 0 : offset;
const size_t lastStrides = LastTypeIsMy<Args...> ? offset + 1 : 0;
const size_t tailFactor = std::accumulate(std::end(strides) - lastStrides, std::end(strides),
size_t { 1 }, std::multiplies<> {});
return index_impl(0, std::begin(strides) + advenceBy, args...) * tailFactor;
}
这是现场演示(通过测试(。
C++14明显缺乏折叠表达式,这通常会使参数包更难使用。值得庆幸的是,我们可以使用数组初始化来伪造fold表达式,这在constexpr
函数中是允许的,所以我们可以不用递归,代码可读性更好。
template<typename... Idxs>
constexpr int indexing_type()
{
size_t integrals = 0;
bool unused[] = {(integrals += std::is_integral<Idxs>::value, false)...};
(void)unused;
bool mys[] = {std::is_same<my, Idxs>::value...};
bool last = mys[sizeof(mys) - 1];
if(integrals == sizeof...(Idxs))
return 1;
if(integrals == sizeof...(Idxs) - 1 && last)
return 2;
return 0;
}
template<typename S, size_t... Is, typename... Idxs>
inline auto mul_reduce(const S& strides, size_t off, std::index_sequence<Is...>, Idxs... idxs)
{
size_t sum = 0;
bool unused[] = {(sum += strides[off + Is] * size_t(idxs), false)...};
(void)unused;
return sum;
}
template<typename S, typename... Idxs>
inline auto index(const S& strides, Idxs... idxs)
-> std::enable_if_t<indexing_type<Idxs...>() == 1, size_t>
{
auto off = strides.size() - sizeof...(Idxs);
return mul_reduce(strides, off, std::make_index_sequence<sizeof...(Idxs)>{}, idxs...);
}
template<typename S, typename... Idxs>
inline auto index(const S& strides, Idxs... idxs)
-> std::enable_if_t<indexing_type<Idxs...>() == 2, size_t>
{
return mul_reduce(strides, 0, std::make_index_sequence<sizeof...(Idxs)>{}, idxs...);
}
indexing_type
告诉您要调度到哪个函数,并且在参数类型错误的情况下,还禁止index
进行过载解析。
在mul_reduce
中,我们滥用了size_t(missing) == 0
的事实,以避免需要从末尾删除它。