Is it possible to develop static for loop in c++?
Asked Answered
A

4

19

Is it possible for something like this to exist?

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

instead of

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

    }
};

The second solution is the only solution I could come up of when I want the compiler to figure out the result at compile time. Do I have an easy way to make the "i" become constant value likethe metaprogramming solution? For me, a simple for loop is much more easier to work with rather than the metaprogramming version.

Abisia answered 11/12, 2012 at 9:4 Comment(5)
You could also write it recursively and use constexpr, if you prefer that type of syntax?Dovap
I tried to make a constexpr version but failed, constexpr only allow one return statement.Abisia
I'm fairly certain that most modern compilers do this optimization automatically, much like they do it for for loops until a constant value (e.g. for(int i = 0; i < 5; i++)). You'd have to check to be sure though.Thyroxine
Then I should learn how to set the optimization flags of the compiler when this become the bottleneck of my programAbisia
It's entirely possible to unroll that code at compile time, although my particular findings are that MSVC can do that already and often results in better machine code since it has more contextRiesman
O
24

Template metaprogramming in C++ is pure functional programming, and in pure functional programming you don't get to use loops like for or while and you don't get to have any mutable data at all. All you have is recursion. To make working with recursion easier, you need to rise abstraction level a bit. The recursive code that you have is fine, but the iteration and work can be split apart:

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

Now that you have this meta-function, you can write your deduce_mask function like this:

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

Visual C++ 2012 with /Ob1 command line switch compiles this code into this:

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

If you can't use lambda functions, you need to write a functor. Functor has one advantage over lambda function - you can specify a calling convention (if you don't mind doing that). If the operator() of the functor has __fastcall calling convention then you will see mov edx, x instead of push x in the assembler code.

Oler answered 9/1, 2013 at 23:39 Comment(2)
Thanks, this answer is pretty elegant(at least for me)Abisia
But aren't all those calls slower than having a normal for loop? Why doesn't the compiler optimize them away?Whitman
D
11

With if constexpr we can improve on AOK's solution.

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

With this we can get rid of that ::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...
});

Unfortunately you still have to write i.value.


Note that this would not be possible without if constexpr because AOK's way would require partial template specialization of static_for.

Daddylonglegs answered 30/8, 2017 at 19:41 Comment(0)
P
6

lego's response, while elegant and awesome, won't compile if you want the index to go into a template - e.g. std::get<i>(some_tuple)

In case you want to implement this additional feature in the future, the below code will work and should be backwards-compatible with lego's solution (except that I use a static apply method instead of 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) {}
};

Now you can do the following:

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

Tested in VC++ 2015. I didn't research why this works, but I can only assume that std::integral_constant<T,...> defines an implicit cast to T using value, but the compiler can't figure out that the implicit cast produces a constexpr, so you have to retrieve the value using i.value, which is a constexpr.

Addressing @tom's question in the comment If you want to iterate over a parameter pack, you can do the following (same implementation):

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

If std::get<N.value>(std::make_tuple(args...)) looks ugly, you can create another constexpr function that minimizes the code.

Priggery answered 22/8, 2017 at 13:30 Comment(5)
I think you need to change (int i) to (auto i), because int::value is ill formedDrama
@Drama Good catch! A result of copying/pasting from multiple sources.Priggery
Can you extend this to do a for loop over parameter pack?Daddylonglegs
I had something a little bit different in my mind. I would like to write things like static_for<int,float,double>::apply([&](auto T){ /* function using types in parameter pack */ })Daddylonglegs
@tom, it's not clear what you need, but it seems like it might be sufficiently different than the original post's question. Open up a new question with a bit more details and link it here; I'll take a look.Priggery
S
0

You should be able to loop over a tuple at compile time usingfor.... My understanding is that you should even be able to loop over struct members as follows:

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

See this video@47:44 from Timur Doumler. Note that not even gcc (or clang) from trunk (or master) support this c++20 kind of compile time loop yet - hence I did not test. BTW, does anyone know a search engine where I can sear for for... with punctuation?

Skillern answered 22/7, 2019 at 20:45 Comment(1)
The for... syntax is not mentioned in Cppref as of C++20 being ratified. However, you can do this using fold expression for the comma, and index_sequence to unpack the tuple.Jampan

© 2022 - 2024 — McMap. All rights reserved.