Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Non-type parameter pack cartesian product, "template argument deduction/substitution failed"

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

#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.

>Solution :

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>>;
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading