Home > Back-end >  Non-type parameter pack cartesian product, "template argument deduction/substitution failed&quo
Non-type parameter pack cartesian product, "template argument deduction/substitution failed&quo

Time:08-16

I was messing with non-type parameter packs and tried to do a cartesian product with them. I arrived at a piece of code that somehow compiles with GCC in C 20 but not C 17, and does not compile with Clang at all. I also tried MSVC, which, like GCC, compiles in C 20 but not C 17, but also generates a lot of assembly code for some reason.

I would like to understand why the code doesn't compile in C 17. The error message isn't useful, it just says template argument deduction/substitution failed: and the line in question...

Here is the simplest snippet I could come up with:

#include <iostream>
#include <utility>

template<auto ...values>
struct ValueParameterPack {};

template<std::size_t I, typename T, T ...values>
constexpr T get(ValueParameterPack<values...>) {
    constexpr T value_array[] = {values...};
    return value_array[I];
}

template<std::size_t I, typename T>
constexpr auto get_v = get<I>(T{});

template<auto ...values1, auto ...values2, std::size_t ...Is>
auto cartesian_product(ValueParameterPack<values1...>, ValueParameterPack<values2...>, std::index_sequence<Is...>)
    -> ValueParameterPack<
        std::make_pair(
            get_v<Is / sizeof...(values2), ValueParameterPack<values1...>>,
            get_v<Is % sizeof...(values2), ValueParameterPack<values2...>>
        )...
    >;

template<auto ...values1, auto ...values2>
auto cartesian_product(ValueParameterPack<values1...>, ValueParameterPack<values2...>)
    -> decltype(cartesian_product(
        ValueParameterPack<values1...>{},
        ValueParameterPack<values2...>{},
        std::make_index_sequence<sizeof...(values1) * sizeof...(values2)>()
    ));

template<typename ValueParameterPack1, typename ValueParameterPack2>
using CartesianProduct = decltype(cartesian_product(ValueParameterPack1{}, ValueParameterPack2{}));

int main() {
    using T = CartesianProduct<ValueParameterPack<1, 2>, ValueParameterPack<3, 4>>;
}

https://godbolt.org/z/sWGv7G7e5

Here is a longer version that actually makes use of the cartesian product in case you're curious, but it's not very important: https://godbolt.org/z/dnYbKMe18

I'm sure there are other ways to achieve what I want, and I welcome any idea, but I'm mostly just curious as to why GCC can compile it with -std=c 20 but not with std=c 17, since I don't think I'm using any C 20 feature. And I want some insights into that error message that provides no additional details.

CodePudding user response:

When you write,

ValueParameterPack<
        std::make_pair(
            get_v<Is / sizeof...(values2), ValueParameterPack<values1...>>,
            get_v<Is % sizeof...(values2), ValueParameterPack<values2...>>
        )...
    >;

you can't pass a std::pair as a template non-type argument. You can, however, pass it as two arguments (if the particular types allow it). So I'd recommend to pass, instead of a ValueParameterPack<pair<int,int>(..)...>, passing a ValueParameterPack<int, int, ...>. Then process it by every pair of element. If you don't like the idea of processing two elements as a step, I've included a workaround solution below using compile-time pairs.

To be clear, when I write, 'can't pass', I don't mean it according to the C standard. It's an error message that occurs with g . Likely a compiler / library issue. While debugging your code, there were at minimum 2 similar issues, so it's not something that never occurs - likely worth reporting.


Here's a way to solve it without std::pair<>:

#include <iostream>
#include <utility>

template<auto i, auto j>
struct mypair {};

template<auto ...values>
struct ValueParameterPack {};

template<std::size_t I, typename T, T ...values>
constexpr T get(ValueParameterPack<values...>) {
    constexpr T value_array[] = {values...};
    return value_array[I];
}

template<std::size_t I, typename T>
constexpr auto get_v = get<I>(T{});

template<size_t I, size_t C, typename T1, typename T2>
using mypair_gen =
    mypair<
        get_v<I / C, T1>,
        get_v<I % C, T2>
    >;

template<auto ...values1, auto ...values2, std::size_t ...Is>
auto cartesian_product(ValueParameterPack<values1...>, ValueParameterPack<values2...>, std::index_sequence<Is...>)
    -> ValueParameterPack<
        mypair_gen<Is, sizeof...(values2), ValueParameterPack<values1...>, ValueParameterPack<values2...>>{}...
    >;

template<auto ...values1, auto ...values2>
auto cartesian_product(ValueParameterPack<values1...>, ValueParameterPack<values2...>)
    -> decltype(cartesian_product(
        ValueParameterPack<values1...>{},
        ValueParameterPack<values2...>{},
        std::make_index_sequence<sizeof...(values1) * sizeof...(values2)>()
    ));

template<typename ValueParameterPack1, typename ValueParameterPack2>
using CartesianProduct = decltype(cartesian_product(ValueParameterPack1{}, ValueParameterPack2{}));

int main() {
    using T = CartesianProduct<ValueParameterPack<1, 2>, ValueParameterPack<3, 4>>;
}
  • Related