Accessing index in parameter pack during template substitution

84 Views Asked by At

I have a template function in C++17, where the N first arguments need to be integral type, and this should be checked during template substitution. (The template function should not exists for arguments that don't meet the condition, so static_assert() in the function body does not work.)

It is a constructor of a class, so SFINAE is done in a template argument.

For fixed N = 2, this works:

struct A {
    template<class Arg1, class Arg2, class... OtherArgs,
        std::enable_if_t<(std::is_integral_v<Arg1> && std::is_integral_v<Arg2>), int> = 0
    >
    A(Arg1 arg1, Arg2 arg2, OtherArgs... otherArgs)
};

However when N is a template argument, there seems to be no obvious way.

To check if all arguments are integrals, this works:

template<std::size_t N>
struct A {
    template<class... Args,
        std::enable_if_t<std::conjunction_v<std::is_integral<Args>...>, int> = 0
    >
    A(Args... args)
};

Is there a tool like INDEX_OF here, which would get the index of an argument in the parameter pack, so that is_integral only needs to be true for the first N arguments?

template<std::size_t N>
struct A {
    template<class... Args,
        std::enable_if_t<std::conjunction_v<
            std::disjunction<
                std::bool_constant< (INDEX_OF(Args) >= N) >,
                std::is_integral<Args>
            >...
        >, int> = 0
    >
    A(Args... args)
};
2

There are 2 best solutions below

4
Jarod42 On BEST ANSWER

You might create constexpr function:

template <std::size_t N, typename... Ts>
static constexpr bool first_are_integral()
{
    constexpr std::array<bool, sizeof...(Ts)> bools{std::is_integral_v<Ts>...};

    for (std::size_t i = 0; i != N; ++i) {
        if (!bools[i]) {
            return false;
        }
    }
    return true;
    // C++20
    // return std::all_of(std::begin(bools), std::begin(bools) + N, [](bool b){ return b; });
}

And then

template <class... Args,
          std::enable_if_t<first_are_integral<N, Args...>(), int> = 0
>
A(Args...){ /*...*/ }

Demo

Alternatively, std::index_sequence is a way to have index and type (from tuple)

template <typename Seq, typename Tuple>
struct first_are_integral_impl;

template <std::size_t...Is, typename Tuple>
struct first_are_integral_impl<std::index_sequence<Is...>, Tuple>
{
    constexpr static bool value =
        (std::is_integral_v<std::tuple_element_t<Is, Tuple>> && ...);
};

template <std::size_t N, typename Tuple>
constexpr bool first_are_integral_v =
    first_are_integral_impl<std::make_index_sequence<N>, Tuple>::value;

Demo

2
Jan Schultke On

You can create a helper template:

template <std::size_t N, typename... T>
inline constexpr bool conjunction_n_v = [] {
    std::size_t i = 0;
    return ((i++ >= N || T::value) && ...);
}();

// ...

static_assert( conjunction_n_v<1, std::is_integral<int>, std::is_integral<float>>);
static_assert(!conjunction_n_v<2, std::is_integral<int>, std::is_integral<float>>);

See live example at Compiler Explorer.

This works kinda like std::conjunction but will not do short-circuiting template instantiations.

Alternative Solution

If the instantiations are greedy, then we may as well use a pack of bool:

template <std::size_t N, bool... B>
inline constexpr bool first_n_true_v = [] {
    std::size_t i = 0;
    return ((i++ >= N || B) && ...);
}();

Keeping the std::conjunction short-ciruiting instantiations

For the sake of compilation speed and simply out of necessity, the fact that std::conjunction short-circuits and doesn't instantiate all given types is useful. This can be implemented using classic recursive templates:

template <std::size_t N, typename... T>
struct conjunction_n : std::false_type {};

template <std::size_t N, typename Head, typename... Tail>
struct conjunction_n<N, Head, Tail...>
  : std::conjunction<std::disjunction<std::bool_constant<N == 0>, Head>,
      conjunction_n<N - 1, Tail...>>
{};

template <std::size_t N, typename... T>
inline constexpr bool conjunction_n_v = conjunction_n<N, T...>::value;