Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply an if at compile time in C++

I am trying to write a general static_for implementation that can accept bounds, an increment function & a comparison function to run a loop through. I have been using this construct with simple loops that increment by 1. In that case it is easy to stop the loop unrolling by simply specializing on the IDX & END being equal.

However when the increment could be with an arbitrary integer, it is not guaranteed that the IDX & END will always be equal. The if conditional is only evaluated at run time. In the code snippet below I was trying to specialize on the std::false_type which stops the recursion. The integral_constant is constructed by evaluating the std::less functional (which could be substituted by the user for any other evaluation). Unfortunately this comparator functional is also evaluated only at run time and therefore the compiler fails. Could someone advise on how to get this to work?

NOTE: Using C++11.

template <int idx, int end, typename eval, int count, typename comparator>
struct static_for_loop {
  template <typename Lambda, typename... Args>
  void operator()(const Lambda& function, Args... args) const {
    if (comparator()(idx, end)) {
      std::integral_constant<int, idx> i;

      function(i, args...);

      constexpr bool lesser = comparator()(idx + count, end);
      static_for_loop<idx + count, end, std::integral_constant<bool, lesser>, count,
                      comparator>()(function, args...);
    }
  }
};

template <int idx, int end, int count, typename comparator>
struct static_for_loop<idx, end, std::false_type, count, comparator> {
  template <typename Lambda, typename... Args>
  void operator()(const Lambda& function, Args... args) const {}
};

template <int idx, int end, int count = 1, typename comparator = std::less<int>>
struct static_for {
  template <typename Lambda, typename... Args>
  void operator()(const Lambda& function, Args... args) const {
    static_for_loop<idx, end, std::true_type, count, comparator>()(function, args...);
  }
};
like image 352
Ammar Husain Avatar asked Apr 07 '16 22:04

Ammar Husain


2 Answers

I find it easier to just wrap everything in an object:

template <int S, int E, int step>
struct iter {
    auto next() { return iter<std::min(E, S+step), E, step>{}; }
};

And then you just have an overload for the case where it's done and the case where it's not:

template <int S, int E, int step, class F, class... Args>
void for_loop(iter<S, E, step> i, F func, Args... args) {
    func(S, args...);

    for_loop(i.next(), func, args...);
}

template <int E, int step, class F, class... Args>
void for_loop(iter<E, E, step>, F, Args... ) {
}

For instance:

// prints 0 4 8
for_loop(iter<0, 10, 4>{}, [](int i){std::cout << i << ' ';});   

Alternatively, could use enable_if to differentiate the cases to avoid the need for min:

template <int S, int E, int step, class F, class... Args>
std::enable_if_t<(S<E)> for_loop(iter<S, E, step>, F func, Args... args)
{
    func(S, args...);
    for_loop(iter<S+step, E, step>{}, func, args...);
}

template <int S, int E, int step, class F, class... Args>
std::enable_if_t<!(S<E)> for_loop(iter<S, E, step>, F , Args... )
{
}

YMMV on which you prefer.

like image 87
Barry Avatar answered Oct 02 '22 23:10

Barry


You can use sfinae to overcome the problem:

template <int idx, int end, typename eval, int count, typename Comparator>
struct static_for_loop {
    template <typename Lambda, typename... Args>
    auto operator()(Lambda&& function, Args&&... args) const
    -> std::enable_if_t<Comparator{}(idx, end)> {
        std::integral_constant<int, idx> i;

        std::forward<Lambda>(function)(i, std::forward<Args>(args)...);

        constexpr bool lesser = comparator{}(idx + count, end);

        static_for_loop<
            idx + count,
            END,
            std::integral_constant<bool, lesser>,
            count,
            Comparator
        >()(std::forward<Lambda>(function), std::forward<Args>(args)...);
    }

    // do nothing when false
    template <typename Lambda, typename... Args>
    auto operator()(Lambda&& function, Args&&... args) const
    -> std::enable_if_t<!Comparator{}(idx, end)> {

    }
};

std::enable_if will select the right function with sfinae. It will act as a compile time if.

I used perfect forwarding too, as your code didn't work in all case, like passing non copiable or a mutable lambda. Now it will.

If you do not have c++14, you can write typename std::enable_if<...>::type instead.

Try to use less all uppercase name, it hurts lisibility.

like image 38
Guillaume Racicot Avatar answered Oct 02 '22 23:10

Guillaume Racicot