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>>;
}