How to apply an if at compile time in C++
Asked Answered
O

3

11

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...);
  }
};
Orosco answered 7/4, 2016 at 22:4 Comment(0)
K
4

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.

Kursh answered 7/4, 2016 at 22:23 Comment(5)
Sorry, I forgot to add I am still using C++11. That would be a great solution for C++14 with generic lambdas.Orosco
@AmmarHusain You don't really need C++14 for any of this, just some convenience shorthands.Kursh
Yes the enable_if would work. I am going to give it a try now.Orosco
I am unable to compile your proposed code. It has a problem with the enable_if usage. Here is what I get from clang: no type named 'type' in 'std::enable_if<false, void>'; 'enable_if' cannot be used to disable this declarationOrosco
@AmmarHusain Not sure how you mean. Here's a demo that works in C++11.Kursh
A
3

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.

Addictive answered 7/4, 2016 at 22:23 Comment(2)
I am unable to compile your proposed code. It has a problem with the enable_if usage. Here is what I get from clang: no type named 'type' in 'std::enable_if<false, void>'; 'enable_if' cannot be used to disable this declarationOrosco
Try update your compiler. Note that you should provide both function, one with the ! in the enable if and one without.Addictive
O
0

Isn't the problem that you are underspecifying comparator? Just specify your API such that comparator<IDX>::type is std::true_type if the loop should continue for IDX, and stop when it's false_type. Your simple loop case then uses template<int IDX> using Comp = std::integral_constant<bool, (IDX < 5)>`.

Onehorse answered 8/4, 2016 at 0:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.