静态循环

时间:2014-10-28 11:12:42

标签: c++ c++11 boost stl c++-amp

我正在编写模板化的短矢量和小矩阵类,它们不限于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功能可能无法移植。

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

修改

Here是我到目前为止所得到的。这是我开始收集的模板元编程技巧的标题,而for循环将是下一个。帮助器需要一个函子,它具有运行索引作为它的第一个参数,并接受各种谓词。只要下一次迭代的谓词成立,它就会继续实例化仿函数。运行索引可以增加任何数字,乘以数字等等。

3 个答案:

答案 0 :(得分:5)

您可以查看Boost Fusion的算法。

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

简单示例: Live On 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)

答案 1 :(得分:2)

这个怎么样:

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

A(稍加修改)示例:Demo

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

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)... };
    }
};

答案 2 :(得分:1)

关于

我仅限于使用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++ compiler bug的理解,我重写了上面的内容,如下所示,它似乎也适用于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的支持,一般的编译时索引for循环,或者,如果你愿意的话,可以实现基于模板的循环展开C ++ 11,这样可以写出这样的代码:

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 实例。它不一定是最好的方法,例如因为较大的代码在缓存中适应的可能性较小(重复:对于优化总是 measure ),并且对于并行性,您可能有特殊要求。您可以查看“Duff的设备”,了解更有限的循环展开技术。

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

免责声明:深夜编码,所以不一定非常完美! : - /