Search code examples
c++templatestemplate-meta-programming

C++ Template recursion that recurses over indices of a boolean function arguments and evaluates function (template meta-programming)


I am interested in evaluating a boolean function, F that takes in N number of boolean arguments. N corresponds to the number of elements in a tuple, Levels. I want to perform a compile-time computation that ensures that F always evaluates to false. The base case is F(false, false, ..., false), where each argument passed in is false. However, for each element in Levels, if it meets a certain condition, I want to also evaluate F with true input corresponding to the index of that element in Levels.

For example, say Levels = (A, B, C, D). And A and D meet the condition. We want to check that the output is always false for F evaluated:

    F(false, false, false, false) must == false
    F(true, false, false, false) must == false
    F(false, false, false, true) must == false

If any F evaluations returns true, then this should raise a static_assert error. Some pseudocode for the structure would be:

template <class F, class... Levels>
class Coiterate<F, std::tuple<Levels...>>
    {
    public:
        explicit inline Coiterate(F f, Levels&... levels)
        {
             // run the validation with respect to F and Levels
             compile_time_validate<F, Levels>();
        }
     } 

I am sure a pattern like this has a name in C++ template-meta-programming, but I had a hard time googling on SO. If anyone has any links to other posts that address this concept, then that would also be helpful. I would like to implement the compile_time_check function. My attempt at some pseudocode that would solve this at runtime is:

// I want to perform the compile-time check here F and Levels, something along these lines

// first evaluate with all inputs `false`
static_assert(F(false, false, ..., false) == false);
for idx, level in enumerate(Levels):
    if constexpr meet_condition(level):
        input_arg = true;
        static_assert(F(false, ..., input_arg, false, ..., false) == false);
     else:
          // do nothing because we only need to evaluate it with `true` at index idx if the condition is met.

[Update - details on function F] The function F is a boolean function that basically defines a set of conjunctions/disjunctions always.

For example:

// this is a conjunction (A \cap B) over two input Levels
auto fn1 = [](std::tuple<bool, bool> t) constexpr { return std::get<0>(t) && std::get<1>(t); };

// this is a combined disjunction and conjunction over four input levels
auto fn2 = [](std::tuple<bool, bool, bool, bool> t) constexpr
    { (return (std::get<0>(t) && std::get<2>(t) && std::get<1>(t)) || std::get<3>(t)); };

Let's take F as fn2, then say the inputs correspond to Levels = (A, B, C, D) and A and D meet some condition, say A::condition_met, then I would want to check the following:

1. static_assert(F(false, false, false, false) == false)
2. static_assert(F(true, false, false, false) == false)

// note that in this case, the static_assert would fail because
// there is an "OR" operation with a `true` value.
3. static_assert(F(false, false, false, true) == false)

It is assumed that condition_met is defined during compile-time for each level in Levels, so this is just a matter of expanding and evaluating F for a series of different boolean input arguments.


Solution

  • If I understand your requirements correctly, this would work:

    template <typename F, typename... Levels>
    void coiterate(F f, Levels const&...) {
        static_assert(f(std::make_tuple((Levels{}, false)...)) == false);
    
        [&]<std::size_t... Is>(std::index_sequence<Is...>) {
            constexpr auto make_single_level_tuple = []<std::size_t I>(std::integral_constant<std::size_t, I>) constexpr {
                constexpr auto levels_tuple = std::make_tuple(Levels{}...);
                constexpr auto false_tuple = std::make_tuple((Levels{}, false)...);
                return std::make_tuple(std::get<Is>(I == Is ? levels_tuple : false_tuple)...);
            };
    
            static_assert(((f(make_single_level_tuple(std::integral_constant<std::size_t, Is>{})) == false) && ...));
        }(std::index_sequence_for<Levels...>{});
    }
    

    Demo

    levels_tuple is a tuple of "condition met" for each level. false_tuple is a tuple of false of the same size. make_single_level_tuple makes a tuple of false except at index I, where it takes from levels_tuple instead. The rest is just a matter of calling f with the correct tuples, and asserting the conjunction of the results.