Ammar Husain
Ammar Husain

Reputation: 1849

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

Upvotes: 11

Views: 1884

Answers (3)

MSalters
MSalters

Reputation: 179779

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

Upvotes: 0

Guillaume Racicot
Guillaume Racicot

Reputation: 41750

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.

Upvotes: 3

Barry
Barry

Reputation: 302643

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.

Upvotes: 4

Related Questions