循环静态

Static for cycle

本文关键字:静态 循环      更新时间:2023-10-16

我正在编写模板化的短向量和小矩阵类,它们不限于具有2-3-4个元素,但可以具有任意数量的元素。

template <typename T, size_t N>
class ShortVector
{
public:
    ...
    template <size_t I> T& get() { return m_data[I]; }
    template <size_t I> const T& get() const { return m_data[I]; }
private:
    T m_data[N];
};

我希望访问接口是静态的,这样我就可以专门化类,以便使用内置的向量寄存器来支持类的大小。(可能是AVX、C++AMP或OpenCL向量。)问题是,为这个类编写所有需要的运算符(一元-、+、-、*、/、句点、长度…)需要大量的模板递归,而我甚至还没有实现矩阵向量和矩阵乘法,在那里我需要嵌套递归。

现在,我有非成员朋友运算符和一个具有各种静态函数(如)的私人成员类

template <size_t I, typename T1, typename T2> struct Helpers
{
    static void add(ShortVector& dst, const ShortVector<T1, N>& lhs, const ShortVector<T2, N>& rhs)
    {
        dst.get<I>() = lhs.get<I>() + rhs.get<I>();
        Helpers<I - 1, T1, T2>::add(dst, lhs, rhs);
    }
    ...
};
template <typename T1, typename T2> struct Helpers < 0, T1, T2 >
{
    static void add(ShortVector& dst, const ShortVector<T1, N>& lhs, const ShortVector<T2, N>& rhs)
    {
        dst.get<0>() = lhs.get<0>() + rhs.get<0>();
    }
    ...
};

为所有运算符编写这样的静态函数和专业化感觉是错误的。以这种方式编写更复杂的操作非常容易出错。我要找的是类似的东西

static_for< /*Whatever's needed to define something like a run-time for cycle*/, template <size_t I, typename... Args> class Functor>();

或者实际上任何让我省略大部分样板代码的东西。我已经开始编写这样一个类,但我无法用合理的专业化来编译它。我觉得我仍然缺乏编写这样一个类(或函数)的技能。我研究过Boost MPL等其他库,但还没有完全致力于使用它。我还研究过std::index_sequence,它可能也很有用。

虽然std::index_sequence看起来是最可移植的解决方案,但它有一个我不愿意仔细查看的主要缺陷。最终,这些类必须与SYCL兼容,这意味着我只能使用C++11,包括模板元编程技术。std::integer_sequence是一个C++14 STL库的添加,虽然语言标准的这种限制只在语言特性方面很重要,但没有什么能阻止STL实现者在实现C++14 STL特性时使用C++14语言特性,因此使用C++14 STL特征可能是不可移植的。

我愿意接受建议,甚至是解决方案。

编辑

以下是我到目前为止提出的内容。这是我开始收集的Template元编程技巧的标题,for循环将是下一行。助手需要一个函子,该函子将运行索引作为其第一个参数,并接受各种谓词。只要下一次迭代的谓词为true,它就会继续实例化函子。运行索引可以增加任何数字,乘以一个数字,等等。

您可以看看Boost Fusion的算法。

所需要的只是将你的类型调整为融合序列。

简单示例:在Coliru上直播

#include <boost/array.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/algorithm.hpp>
#include <boost/fusion/include/io.hpp>
#include <iostream>
int main()
{
    using namespace boost;
    boost::array<int, 4> iv4 { 1,2,3,4 };
    boost::array<double, 4> id4 { .1, .2, .3, .4 };
    auto r = fusion::transform(iv4, id4, [](auto a, auto b) { return a+b; });
    std::cout << r;
}

打印:

(1.1 2.2 3.3 4.4)

这个呢:

template <size_t I, typename Functor, typename = std::make_index_sequence<I>>
struct Apply;
template <size_t I, typename Functor, std::size_t... Indices>
struct Apply<I, Functor, std::index_sequence<Indices...>> :
    private std::tuple<Functor> // For EBO with functors
{
    Apply(Functor f) :  std::tuple<Functor>(f) {}
    Apply() = default;
    template <typename InputRange1, typename InputRange2, typename OutputRange>
    void operator()(OutputRange& dst,
                    const InputRange1& lhs, const InputRange2& rhs) const
    {
        (void)std::initializer_list<int>
        { (dst.get<Indices>() = std::get<0>(*this)(lhs.get<Indices>(),
                                                   rhs.get<Indices>()), 0)... };
    }
};

用途可能是

Apply<4,std::plus<>>()(dest, lhs, rhs); // Size or functor type 
                                        // can be deduced if desired

一个(稍作修改)示例:演示

如果函子状态以任何方式阻碍你,你也可以删除它:

template <size_t I, typename Functor, typename = std::make_index_sequence<I>>
struct Apply;
template <size_t I, typename Functor, std::size_t... Indices>
struct Apply<I, Functor, std::index_sequence<Indices...>>
{
    template <typename InputRange1, typename InputRange2, typename OutputRange>
    void operator()(OutputRange& dst,
                    const InputRange1& lhs, const InputRange2& rhs) const
    {
        (void)std::initializer_list<int>
        { (dst.get<Indices>() = Functor()(lhs.get<Indices>(),
                                          rhs.get<Indices>()), 0)... };
    }
};

关于

"我被限制使用C++11,包括模板元编程技术。std::integer_sequence是C++14 STL库添加的[…]

…你可以用例如g++编译器:来做到这一点

namespace my {
    using std::tuple;
    using std::tuple_cat;
    template< int i >
    struct Number_as_type_ {};
    template< int... values >
    using Int_sequence_ = tuple< Number_as_type_<values>... >;
    template< class Int_seq_a, class Int_seq_b >
    using Concat_ = decltype( tuple_cat( Int_seq_a(), Int_seq_b() ) );
    template< int max_index >
    struct Index_sequence_t_
    {
        using T = Concat_<
            typename Index_sequence_t_<max_index-1>::T, Int_sequence_<max_index>
            >;
    };
    template<>
    struct Index_sequence_t_<0> { using T = Int_sequence_<0>; };
    template< int n_indices >
    using Index_sequence_ = typename Index_sequence_t_<n_indices - 1>::T;
}  // namespace my

不幸的是,Visual C++12.0(2013)在上述Int_sequence_的模板参数推导上受阻。显然,这与错误地将模板化的using视为类中自动引用的局部typedef有关。无论如何,在理解了Visual C++编译器错误的基础上,我重写了上面的内容,如下所示,它似乎也能很好地与Visual C++一起工作:

与Visual C++12.0配合使用效果更好的版本
namespace my {
    using std::tuple;
    using std::tuple_cat;
    template< int i >
    struct Number_as_type_ {};
    template< int... values >
    struct Int_sequence_
    {
        using As_tuple = tuple< Number_as_type_<values>... >;
    };
    template< int... values >
    auto int_seq_from( tuple< Number_as_type_<values>... > )
        -> Int_sequence_< values... >;
    template< class Int_seq_a, class Int_seq_b >
    using Concat_ = decltype(
        int_seq_from( tuple_cat(
            typename Int_seq_a::As_tuple(), typename Int_seq_b::As_tuple()
            ) )
        );
    template< int n_indices >
    struct Index_sequence_t_
    {
        using T = Concat_<
            typename Index_sequence_t_<n_indices-1>::T, Int_sequence_<n_indices-1>
            >;
    };
    template<>
    struct Index_sequence_t_<1> { using T = Int_sequence_<0>; };
    template< int n_indices >
    using Index_sequence_ = typename Index_sequence_t_<n_indices>::T;
}  // namespace my

有了以上基于C++11的支持,可以在C++11中实现一个通用的编译时索引for循环,或者,如果你愿意的话,可以实现基于模板的循环展开,这样就可以编写这样的代码:

template< int i >
struct Add_
{
    void operator()( int sum[], int const a[], int const b[] ) const
    {
        sum[i] = a[i] + b[i];
    }
};
#include <iostream>
using namespace std;
auto main() -> int
{
    int sum[5];
    int const a[] = {1, 2, 3, 4, 5};
    int const b[] = {100, 200, 300, 400, 500};
    my::for_each_index<5, Add_>( sum, a, b );
    for( int x: sum ) { cout << x << ' '; } cout << endl;
}

然而,请注意,虽然这可能是自切片披萨以来的第二好东西,但我怀疑任何一个相当好的编译器都会理所当然地进行循环展开优化,即引入这一点额外的复杂性不一定会带来任何好处。

与优化一样,执行MEASURE


此设计执行总循环展开,也就是说,不是执行具有不同索引的循环体的n,而是获得具有不同索引值的循环体实例的n。这不一定是最好的方法,例如,因为较大的代码在缓存中拟合的机会较小(重复:对于优化,总是度量),对于并行性,您可能有特殊要求。您可以查看“达夫;s设备";用于更有限的循环展开的技术。

namespace my {
    using std::forward;
    using std::initializer_list;
    template< class Type >
    void evaluate( initializer_list< Type > const& ) {}
    namespace impl {
        template< template <int> class Functor_, class... Args >
        struct Call_with_numbers_
        {
            template<  int... numbers >
            void operator()( Int_sequence_<numbers...> const&, Args&&... args ) const
            {
                evaluate( {(Functor_<numbers>()( args... ), 0)...} );
            }
        };
    }  // namespace impl
    template< int n, template<int> class Functor_, class... Args >
    void for_each_index( Args&&... args )
    {
        using Seq = Index_sequence_<n>;
        Seq s;
        impl::Call_with_numbers_< Functor_, Args... >()( s, forward<Args>( args )... );
    }
}  // namespace my

免责声明:编码到深夜,所以不一定很完美!://