简体   繁体   中英

Compile time nested loop using `std::integer_sequence`

In the code below, I have worked out a compile time nested loop that calls a print function that prints the number to screen. The 1D and 2D version work, but the 3D version triggers a note: candidate template ignored: failed template argument deduction error. What do I do wrong?

#include <functional>
#include <iostream>


// 1D processing.
template<int I>
constexpr void print()
{
    std::cout << I << std::endl;
}


template<int... Is>
constexpr void test(
        std::integer_sequence<int, Is...> is)
{
    (print<Is>(), ...);
}
// End 1D.


// 2D processing.
template<int I, int J>
constexpr void print()
{
    std::cout << I << ", " << J << std::endl;
}


template<int I, int... Js>
constexpr void print(
        std::integer_sequence<int, Js...> js)
{
    (print<I, Js>(), ...);
}


template<int... Is, int... Js>
constexpr void test(
        std::integer_sequence<int, Is...> is,
        std::integer_sequence<int, Js...> js)
{
    (print<Is, Js...>(js), ...);
}
// End 2D.


// 3D processing.
template<int I, int J, int K>
constexpr void print()
{
    std::cout << I << ", " << J << ", " << K << std::endl;
}

template<int I, int J, int... Ks>
constexpr void print(
        std::integer_sequence<int, Ks...> ks)
{
    (print<I, J, Ks>(), ...);
}


template<int I, int... Js, int... Ks>
constexpr void print(
        std::integer_sequence<int, Js...> js,
        std::integer_sequence<int, Ks...> ks)
{
    (print<I, Js, Ks...>(ks), ...);
}


template<int... Is, int... Js, int... Ks>
constexpr void test(
        std::integer_sequence<int, Is...> is,
        std::integer_sequence<int, Js...> js,
        std::integer_sequence<int, Ks...> ks)
{
    // THIS CALL FAILS DURING COMPILATION.
    (print<Is, Js..., Ks...>(js, ks), ...);
}
// End 3D.


int main()
{
    constexpr std::integer_sequence<int, 1, 2, 4, 8> is{};
    constexpr std::integer_sequence<int, 1, 2, 4> js{};
    constexpr std::integer_sequence<int, 32, 64> ks{};

    test(is); // 1D example.
    test(is, js); // 2D example.
    test(is, js, ks); // 3D example, FAILS.

    return 0;
}

The reason for it failing is that the overload of your function print which takes three template arguments has two variadic template parameter packs template<int I, int... Js, int... Ks> . Multiple template parameter packs are only valid if the compiler can deduct them from the function arguments (see temp.param/14 ):

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list ([dcl.fct]) of the function template or has a default argument ([temp.deduct]). A template parameter of a deduction guide template ([temp.deduct.guide]) that does not have a default argument shall be deducible from the parameter-type-list of the deduction guide template.

When explicitly specifying the template parameters the compiler will therefore match them greedily to the first parameter pack . This is even the case when combining type and non-type parameters . Only then deduction occurs from non-template function arguments .

This means in your case

(print<Is, Js..., Ks...>(js, ks), ...);

will actually ignore the wished candidate template

prog.cc:64:16: note: candidate template ignored: failed template argument deduction

as something like

print<I, Js..., Ks...>()

would actually match something like

template <int I, int... Ts>
constexpr void print()

Js... and Ks... will be combined to Ts... = {Js..., Ks...} leaving none of the specified template parameters to the following packs as - by the standard - it must be possible to deduce them from the non-template function arguments.


In order to see why allowing a syntax like in your initial code could be problematic just imagine a template function with two parameter packs Ts and Ss , where Ss can be deducted from the function arguments but Ts can't be deducted and instead has to specified explicitly,

template <typename... Ts, typename... Ss>
void func(Ss... ss) {
  return;
}

which is called with

func<int,double>(int{1}, double{7.0});

This would be ambiguous ( Ts = {int, double}, Ss = {int, double} or Ts = {}, Ss = {int, double} ) and therefore lead to problems with the logic in your initial code but is unambiguous ( Ts = {int, double}, Ss = {int, double} ) if you match the the parameters greedily and only then deduct from the function arguments.


In your case simply removing the right-most parameter pack

(print<Is, Js...>(js, ks), ...);

or both of them

(print<Is>(js, ks), ...);

makes the compilation succeed as Js and Ks can both be deducted from the function arguments js and ks . Try it here!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM