19

这样的事情有可能存在吗?

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];    
    }
}

代替

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[])
    {

    }
};

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

4

4 回答 4

23

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
    { }
};

现在你有了这个元函数,你可以像这样编写你的 deuce_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];    
    });
}

带有 /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)  
...

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

于 2013-01-09T23:39:41.930 回答
10

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

有了这个我们可以摆脱那个::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...
});

不幸的是,您仍然必须编写i.value.


请注意,如果没有这将是不可能的,if constexpr因为 AOK 的方式需要对static_for.

于 2017-08-30T19:41:06.070 回答
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) {}
};

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

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

在 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

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

于 2017-08-22T13:30:31.180 回答
0

您应该能够在编译时使用for.... 我的理解是,您甚至应该能够循环遍历结构成员,如下所示:

#include <iostream>
#include <string>
#include <tuple>
#include <utility>

struct Agg { int a;
             std::string b;
             double c;};

template <typename... T>
void print (std::tuple<T...> const & t) {
        for... (auto const & member: t)
                std::cout << member << '\n';
}

int main () {
        auto agg = Agg{1,"bla",2.1};    
        print (agg);
}

请参阅 Timur Doumler 的此视频@47:44。请注意,甚至来自trunk(或master)的gcc(或clang)都不支持这种c ++ 20类型的编译时间循环 - 因此我没有测试。顺便说一句,有人知道我可以for...用标点符号搜索的搜索引擎吗?

于 2019-07-22T20:45:18.160 回答