是否有可能在c ++中开发静态for循环?

Ste*_*ing 18 c++ for-loop template-meta-programming

是否有可能存在这样的事情?

template<int Channel>
void deduce_mask(Matrix const &src, int mask[])
{
    //I hope i could become a constant and the compiler would unroll the loop at compile time        
    for(int i = Channel; i != -1; --i)
    {            
        //mapper is a helper class which translate two and three dimension into one dimension index
        //constexpr makes it possible to find out the index at compile time
        mask[mapper(0, 1, i)] = src(row - 1, col)[i];
        mask[mapper(1, 1, i)] = src(row, col)[i];
        mask[mapper(2, 1, i)] = src(row + 1, col)[i];    
    }
}
Run Code Online (Sandbox Code Playgroud)

代替

template<int Channel>
class deduceMask
{
public:
    static void deduce_mask(matrix const &src, int mask[]);
};

template<int Channel>
void deduce_mask(matrix const &src, int mask[])
{                
    mask[mapper(0, 1, Channel)] = src(row - 1, col)[Channel];
    mask[mapper(1, 1, Channel)] = src(row, col)[Channel];
    mask[mapper(2, 1, Channel)] = src(row + 1, col)[Channel];    

    deduceMask<Channel - 1>::deduce_mask(src, mask);
}

template<>
class deduceMask<-1>
{
public:
    static void deduce_mask(matrix const &src, int mask[])
    {

    }
};
Run Code Online (Sandbox Code Playgroud)

当我希望编译器在编译时找出结果时,第二个解决方案是我能想到的唯一解决方案.在元编程解决方案中,我是否有一种简单的方法可以使"i"成为恒定值?对我来说,一个简单的for循环比使用元编程版本更容易使用.

leg*_*ego 21

C++中的模板元编程是纯函数式编程,在纯函数式编程中,你不能使用像for或while这样的循环,你根本就没有任何可变数据.你只有递归.为了使递归更容易,您需要提高抽象级别.您拥有的递归代码很好,但迭代和工作可以分开:

template <int First, int Last>
struct static_for
{
    template <typename Fn>
    void operator()(Fn const& fn) const
    {
        if (First < Last)
        {
            fn(First);
            static_for<First+1, Last>()(fn);
        }
    }
};

template <int N>
struct static_for<N, N>
{
    template <typename Fn>
    void operator()(Fn const& fn) const
    { }
};
Run Code Online (Sandbox Code Playgroud)

现在您已经拥有了这个元函数,您可以像这样编写deduce_mask函数:

template<int Channel>
void deduce_mask(Matrix const &src, int mask[])
{
    static_for<0, Channel>()([&](int i)
    {            
        mask[mapper(0, 1, i)] = src(row - 1, col)[i];
        mask[mapper(1, 1, i)] = src(row, col)[i];
        mask[mapper(2, 1, i)] = src(row + 1, col)[i];    
    });
}
Run Code Online (Sandbox Code Playgroud)

带有/ Ob1命令行开关的Visual C++ 2012将此代码编译为:

push        0  
call        <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)  
push        1  
call        <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)  
push        2  
call        <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)  
push        3  
call        <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)  
push        4  
call        <lambda_7588286c1d4f3efe98a2e307bd757f8e>::operator() (010C1270h)  
...
Run Code Online (Sandbox Code Playgroud)

如果你不能使用lambda函数,你需要编写一个仿函数.Functor比lambda函数有一个优势 - 你可以指定一个调用约定(如果你不介意这样做).如果仿函数的operator()具有__fastcall调用约定,那么您将看到mov edx, x而不是push x汇编代码.

  • 但是并不是所有那些`call的速度慢于正常的for循环?为什么编译器不优化它们? (7认同)

tom*_*tom 8

随着if constexpr我们可以提高AOK的解决方案.

template <int First, int Last, typename Lambda>
inline void static_for(Lambda const& f)
{
    if constexpr (First < Last)
      {
         f(std::integral_constant<int, First>{});
         static_for<First + 1, Last>(f);
      }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我们可以摆脱它 ::apply

static_for<0, Channel>([&](auto i) 
{            
    // code...
    mask[mapper(0, 1, i)] = src(row - 1, col)[i]; // Notice that this does not change
    std::get<i.value>(some_tuple); // But here you must get the member .value
    // more code...
});
Run Code Online (Sandbox Code Playgroud)

不幸的是你还是要写i.value.


请注意,如果没有if constexprAOK的方式需要部分模板特化,这是不可能的static_for.


AOK*_*AOK 5

乐高的响应虽然优雅而令人敬畏,但如果您希望索引进入模板,则不会编译 - 例如 std::get<i>(some_tuple)

如果您想在将来实现此附加功能,以下代码将起作用,并且应该向后兼容乐高的解决方案(除了我使用静态应用方法而不是 operator()):

template <int First, int Last>
struct static_for
{
    template <typename Lambda>
    static inline constexpr void apply(Lambda const& f)
    {
        if (First < Last)
        {
            f(std::integral_constant<int, First>{});
            static_for<First + 1, Last>::apply(f);
        }
    }
};
template <int N>
struct static_for<N, N>
{
    template <typename Lambda>
    static inline constexpr void apply(Lambda const& f) {}
};
Run Code Online (Sandbox Code Playgroud)

现在您可以执行以下操作:

static_for<0, Channel>::apply([&](auto i) // Changed from '(int i)'. In general, 'auto' will be a better choice for metaprogramming!
{            
    // code...
    mask[mapper(0, 1, i)] = src(row - 1, col)[i]; // Notice that this does not change
    std::get<i.value>(some_tuple); // But here you must get the member .value
    // more code...
});
Run Code Online (Sandbox Code Playgroud)

在 VC++ 2015 中进行了测试。我没有研究为什么会这样,但我只能假设它std::integral_constant<T,...>定义了一个隐式转换为Tusing 值,但是编译器无法确定隐式转换会产生 a constexpr,因此您必须检索该值使用i.value,这是一个constexpr.

在评论中解决@tom 的问题 如果要迭代参数包,可以执行以下操作(相同的实现):

template<typename... Args>
inline constexpr auto foo(const Args&... args)
{
    static_for<0,sizeof...(Args)>::apply([&](auto N)
    {
        std::cout << std::get<N.value>(std::make_tuple(args...));
    });
}

foo(1,"a",2.5); // This does exactly what you think it would do
Run Code Online (Sandbox Code Playgroud)

如果std::get<N.value>(std::make_tuple(args...))看起来很难看,您可以创建另一个constexpr最小化代码的函数。