【问题标题】:Static for cycle静态循环
【发布时间】:2014-10-28 11:12:42
【问题描述】:

我正在编写模板化的短向量和小矩阵类,它们不限于具有 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 循环将是下一个。助手需要一个以运行索引为第一个参数的函子,并接受各种谓词。只要下一次迭代的谓词成立,它就会继续实例化函子。可以将运行索引增加任意数字、乘以数字等。

【问题讨论】:

  • 提示:没有人说“循环”。这是一个循环结构
  • 你可以让你的 helper 类接受一个函数对象,该函数对象实际上决定了计算的内容,然后在实际 operators 的定义中使用 lambda 调用它
  • 查看 Eigen,它可能会为您节省大量工作:eigen.tuxfamily.org/index.php?title=Main_Page
  • 这是什么意思:“我想让访问接口是静态的,这样我就可以专门化这个类来使用内置向量寄存器来支持班上”。究竟什么是“内置向量寄存器”?其余的是什么意思?投票结束,因为不清楚。
  • 我 认为 OP 使用static(错误地)表示使用模板参数而不是函数参数,并希望专门化操作以使用 SIMD或兼容数组类型和长度的 GPU 操作。

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


【解决方案1】:

您可以看看 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)

【讨论】:

  • 恐怕没有例子,这是评论,不是答案。
  • 很公平。我会把它留给 OP 询问他们是否也想要一个适应的例子:-)
  • @Angew 一般来说,OP 可以想要很多东西。不过,公平的努力是该网站的一项要求(老实说,如果我完全清楚操作应该做什么,我通常会简单地将其实现为演示)
  • 这是为了解决什么问题?我可以用更少的代码写出两个数组的成对项总和。甚至可能使用std::valarray(虽然从未使用过它,所以它是第一个)。我想从根本上说,我在问,您在 OP 明显随机的措辞中看到了什么问题?
  • @Cheersandhth.-Alf 数组是他的实现细节,碰巧已经适应了(通过adapted.hpp)。 (我不需要为自己能看到潜在问题的能力辩护,对。如果你认为没有问题,你可以投反对票)
【解决方案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

一个(稍作修改)示例: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)... };
    }
};

【讨论】:

  • 很好。当我决定对我的示例使用 poly-lambdas 时,我忘记了我仍然可以使用 std::plus&lt;&gt; :)
  • 虽然我担心这段代码在我的情况下无法编译(稍微解释一下),但它确实给了我很好的想法。从 std::tuple 继承将使 Apply 类与 AMP 不兼容,因为并非元组的所有成员函数都用 restrict(amp) 修饰。 (事实上​​,它们都不是)但是 operator() 的主体确实有一些不错的想法: std::initializer_list{(/*实际工作*/)...} 的 static_cast 我觉得很优雅.值得投票。
  • @Meteorhead 无需tuple 即可轻松修改代码。 tuple 仅用于有效地保存具有状态的函子。编辑了答案。
【解决方案3】:

关于

” 我仅限于使用 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 个具有不同索引值的循环体实例.这不一定是最好的方法,例如因为较大的代码适合缓存的机会较小(重复:对于优化总是测量),并且对于并行性,您可能有特殊要求。您可以查看“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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-05
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    • 2021-02-24
    • 2018-03-30
    • 2015-08-28
    相关资源
    最近更新 更多