I have a struct sequence
similar to std::integer_sequence
, but it can hold values of multiple types as non-type template parameters (sort of like a compile-time tuple). It is implicitly constructible from std::integer_sequence
by using a constexpr
constructor and a user-defined deduction guide. I also have a function apply_constexpr
that applies a non-type template parameter pack to a function, similarly to std::apply
. Here they are:
template <auto...>
struct sequence {
template <typename T, T... Ts>
constexpr sequence(std::integer_sequence<T, Ts...>) noexcept {
}
};
template <typename T, T... Ts>
sequence(std::integer_sequence<T, Ts...>) -> sequence<Ts...>;
template<typename Fn, auto... Vals>
constexpr std::invoke_result_t<Fn, decltype(Vals)...>
apply_constexpr(Fn&& fn, sequence<Vals...>)
noexcept(std::is_nothrow_invocable_v<Fn, decltype(Vals)...>) {
return fn(std::forward<decltype(Vals)>(Vals)...);
}
They can be used like this:
static_assert(apply_constexpr(
[&](auto&&... i) { return ((f(i) == g(i)) && ...); },
sequence{ std::make_index_sequence<100>() })
);
When the sequence
is constructed explicitly like above, everything is fine. However because the constructor is not explicit
the following also works:
sequence s = std::make_index_sequence<100>();
But despite it working, the following does not work (candidate template ignored: could not match 'sequence' against 'integer_sequence'
):
static_assert(apply_constexpr(
[&](auto&&... i) { return ((f(i) == g(i)) && ...); },
std::make_index_sequence<100>())
// no "sequence{ ... }" here, trying to rely on implicit conversion
);
Why doesn't it work and what could I do to make it work?
You could make an overload that forward to your first apply_constexpr
function. Something like this.
template<typename Fn, typename Is>
constexpr auto apply_constexpr(Fn&& fn, Is s)
noexcept(noexcept(apply_constexpr(fn, sequence{s}))) {
return apply_constexpr(std::forward<Fn>(fn), sequence{s});
}
The reason it doesn't work in the first place is that template deduction is always on the exact type. When deducing the compiler will not consider conversions. If you pass a std::integer_sequence
, that's what the compiler will deduce.
Doing conversions and deductions in one step is not supported by the language.