2

In C++, I'm trying to write something similar to boost-mp11's mp_for_each. However, whilst mp_for_each always calls the supplied function for every T in the given mp_list<Ts...>, I'm trying to come up with a solution that stops traversal once a run-time call to the function yields a value evaluating to false in an if-statement.

See the implementation of mp_for_each and a usage example:

Implementation on GitHub

Usage example in Boost reference manual

Apparently, the implementation of mp_for_each manages to pass the function argument as a constant expression, thus enabling the user to apply it where a constant expression is required. Whilst I took a different approach incorporating template tail recursion, I expected the function argument to be passed as a constant expression as welll. However, GCC complains that it "is not a constant expression".

My code looks like this:

#include <cstdlib>
#include <iostream>
#include <typeinfo>
#include <utility>
#include <boost/mp11.hpp>

template<std::size_t T_counter>
struct WhileGreaterZero
{
    template<typename T_Function>
    constexpr WhileGreaterZero(T_Function&& function)
    {
        if (function(T_counter))  // pass function argument
            WhileGreaterZero<T_counter - 1>(std::forward<T_Function>(function));
    }
};

template<>
struct WhileGreaterZero<0>
{
    template<typename T_Function>
    constexpr WhileGreaterZero(T_Function&&) {}
};

int main()
{
    using boost::mp11::mp_at_c;
    using boost::mp11::mp_list;
    using boost::mp11::mp_size;

    using Types = mp_list<bool, int, double>;

    WhileGreaterZero<mp_size<Types>::value - 1>(
        [](auto counter) {  // function parameter
            using Type = mp_at_c<Types, counter>;

            if (typeid(Type) == typeid(int))
                return false;

            return true;
        }
    );
}

When compiling with g++ 7.4.0, the following error is encountered (formatted to my taste):

$ g++ -std=c++17 -I/path/to/boost

wgz.cpp:
    In substitution of ‘
        template<
            class L,
            long unsigned int I
        >
        using mp_at_c =
            typename boost::mp11::detail::mp_if_c_impl<
                (I < typename boost::mp11::detail::mp_size_impl<L>::type:: value),
                boost::mp11::detail::mp_at_c_impl<L, I>,
                void
            >::type::type
        [
            with L = boost::mp11::mp_list<bool, int, double>;
            long unsigned int I = counter
        ]
    ’:

wgz.cpp:42:49:
    required from ‘
        main()::<lambda(auto:1)>
        [with auto:1 = long unsigned int]
    ’

wgz.cpp:14:21:
    required from ‘
        constexpr WhileGreaterZero<T_counter>::WhileGreaterZero(T_Function&&)
        [
            with T_Function = main()::<lambda(auto:1)>;
            long unsigned int T_counter = 2
        ]
    ’

wgz.cpp:49:5:
    required from here

wgz.cpp:42:49:
    error: ‘counter’ is not a constant expression
             using Type = mp_at_c<Types, counter>;
                                                 ^
wgz:42:49:
    note: in template argument for type ‘long unsigned int’

Why is counter not considered as a constant expression in my code? What's the crucial difference between mp11's code and mine in this regard?

ngmr80
  • 135
  • 5

2 Answers2

2

Change

function(T_counter)

to

function(std::integral_constant< std::size_t, T_counter >{})

within function the argument is not a compile time value. But an integral_constant that isn't a compile time value can be cast to an integer, and that integer is a compile time constant, because it doesn't depend on this.

A related trick is:

template<std::size_t...Is>
constexpr auto indexes( std::index_sequence<Is...> ={} ) {
  return std::make_tuple( std::integral_constant<std::size_t, Is>{}... );
}

then you can do:

template<std::size_t N, class F>
void While( F&& f ) {
  std::apply( [&](auto...Is) {
    (f( Is ) && ...);
  }, indexes( std::make_index_sequence<N>{} ) );
}

Live example, no recursion.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • was trying to substitute that with constexpr lambda and it didn't work, my bad. Neat trick. – Swift - Friday Pie Feb 07 '20 at 20:55
  • @Yakk Thank you for adding another variant to your answer, even after I had already accepted it. I really like this new variant utilizing a fold-expression and short-circuiting. It's much more elegant than mine. :) – ngmr80 Feb 07 '20 at 23:58
  • @ngmr80 The downside is that creating a 100 element tuple has a significant compile-time cost. You can get the indexes->apply thing much, much cheaper other ways. – Yakk - Adam Nevraumont Feb 08 '20 at 03:16
0

Parameter of lambda is a parameter of function, its value is not passed at compile-time. At very least this line is ill-formed:

 using Type = mp_at_c<Types, counter>;

You have to wait for template lambdas or implement your own functor

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42