The following code tries to make compile-time decisions based on the last argument passed in a parameter pack. It contains a comparison if the number of parameter-pack arguments is > 0 and then tries to get the last element of it. However, the constructed tuple is accessed at an invalid index which is supposedly bigger than the max tuple index (as the static_assert
shows).
How is that possible if I do cnt-1
?
#include <cstdio>
#include <concepts>
#include <utility>
#include <tuple>
template <typename... Args>
auto foo(Args&&... args)
{
auto tuple = std::forward_as_tuple(std::forward<Args>(args)...);
constexpr std::size_t cnt = sizeof...(Args);
if constexpr (cnt > 0 && std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt-1, decltype(tuple)>>, int>) {
printf("last is int\n");
} else {
printf("last is not int\n");
}
}
int main()
{
foo(2);
foo();
}
Error:
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/tuple: In instantiation of 'struct std::tuple_element<18446744073709551615, std::tuple<> >':
<source>:13:25: required from 'auto foo(Args&& ...) [with Args = {}]'
<source>:24:8: required from here
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/tuple:1357:25: error: static assertion failed: tuple index must be in range
1357 | static_assert(__i < sizeof...(_Types), "tuple index must be in range");
| ~~~~^~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/tuple:1357:25: note: the comparison reduces to '(18446744073709551615 < 0)'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/tuple:1359:13: error: no type named 'type' in 'struct std::_Nth_type<18446744073709551615>'
1359 | using type = typename _Nth_type<__i, _Types...>::type;
| ^~~~
CodePudding user response:
Short-circuiting that stops the rhs from being evaluated (having its value computed at runtime) doesn't stop it from being instantiated (having template arguments substituted into templates, checking them for validity, all at compile-time).
I don't see any particular reason why it couldn't work the way you expect, it just wasn't added to the language (yet).
As @wohlstad said, if constexpr
is the solution:
if constexpr (cnt > 0)
{
if constexpr (std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt - 1, decltype(tuple)>>, int>)
{
...
The first if
must be constexpr, while the second only should (in your scenario).
CodePudding user response:
You can force the compiler to evaluate the 2nd condition only if cnt > 0
using if constexpr
(available since c 17) and separating into 2 nested if
s:
#include <cstdio>
#include <concepts>
#include <utility>
#include <tuple>
template <typename... Args>
auto foo(Args&&... args)
{
auto tuple = std::forward_as_tuple(std::forward<Args>(args)...);
constexpr std::size_t cnt = sizeof...(Args);
//-----vvvvvvvvv---------
if constexpr (cnt > 0)
{
if (std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt - 1, decltype(tuple)>>, int>) {
printf("last is int\n");
}
else {
printf("last is not int\n");
}
}
}
int main()
{
foo(2);
foo();
}
Output:
last is int