0

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?

janekb04
  • 4,304
  • 2
  • 20
  • 51
  • Please let me know if this question can have a better title. I didn't really know how to title it myself because I don't know what exactly causes the issue. – janekb04 Jan 02 '21 at 19:22
  • 1
    "Initialization of function parameter" is not listed as one of the contexts where CTAD is performed on [cppreference](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction), and also you don't need that deduction guide, since it's automatically made from the constructor. – HTNW Jan 02 '21 at 19:28
  • @HTNW Thanks, the failure of CTAD in this context must be the reason. Regarding the deduction guide though, it is needed. Without it the parameter pack is deduced to be empty. – janekb04 Jan 02 '21 at 19:38
  • 1
    Oh, oops, didn't notice that the constructor type arguments weren't related to the class's. Why not `template struct sequence { template constexpr sequence(std::integer_sequence) noexcept { } };`? – HTNW Jan 02 '21 at 19:44
  • @HTNW I didn't think of that. It seems like the way to go. I seem to overcomplicate things often. – janekb04 Jan 02 '21 at 19:49

1 Answers1

1

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.

super
  • 12,335
  • 2
  • 19
  • 29