constexpr 上下文中运算符 && 的奇怪行为

strange behaviour of operator && in constexpr context

提问人:glades 提问时间:1/25/2023 最后编辑:wohlstadglades 更新时间:1/25/2023 访问量:124

问:

以下代码尝试根据参数包中传递的最后一个参数做出编译时决策。如果参数包参数的数量为 0,则包含>,然后尝试获取其最后一个元素的比较。但是,构造的元组是在无效索引下访问的,该索引假定大于最大元组索引(如图所示)。
如果我这样做,这怎么可能?
static_assertcnt-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();
}

错误:

/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;
      |             ^~~~
C++ 元组 if-constexpr 和运算符

评论

0赞 glades 1/25/2023
@Someprogrammerdude编辑。在制作示例时,我应该更加小心
0赞 SimonC 1/25/2023
我很确定这不是关于“AND”运算符,而是关于右值运算符,不是吗?
1赞 463035818_is_not_an_ai 1/25/2023
@SimonC没有“右值运算符”,问题就来了and

答:

3赞 wohlstad 1/25/2023 #1

只有在使用 if constexpr(从 c++17 开始可用)并分隔为 2 个嵌套 s 时,才能强制编译器计算第 2 个条件:cnt > 0if

#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();
}

输出:

last is int

评论

0赞 glades 1/25/2023
它确实适用于两个嵌套的 if constexpr(),但为什么它不适用于 && 运算符?
0赞 wohlstad 1/25/2023
我不确定(对 qeustion 投了赞成票,因为我认为这是一个很好的)。但我认为你可能会发现我的答案很有用,因为它确实提供了一种实现你需要的方法。
0赞 glades 1/25/2023
谢谢,是的,我当然会这样做。我只是好奇:)
6赞 HolyBlackCat 1/25/2023 #2

阻止 rhs 被评估短路(在运行时计算其值)不会阻止它被实例化(将模板参数替换为模板,检查它们的有效性,所有这些都在编译时完成)。

我看不出它不能按你期望的方式工作的任何特殊原因,它只是没有被添加到语言中(还没有)。

正如@wohlstad所说,解决方案是:if constexpr

if constexpr (cnt > 0)
{
    if constexpr (std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt - 1, decltype(tuple)>>, int>)
    {
        ...

第一个必须是 constexpr,而第二个应该(在您的方案中)。if

评论

1赞 glades 1/25/2023
有趣的是,我没有想到无论选择何种路径都会发生实例化,但这可以解释它。Afaik clang 也有同样的行为。至于 std::same_as 你可以这样使用它。这是一个概念,而不是类型特征(尽管基于您提到的类型特征)。
0赞 HolyBlackCat 1/25/2023
@glades啊,脸掌。编辑。
1赞 Turtlefight 2/9/2023
为什么需要对 rhs 进行实例化,一个潜在的原因可能是可能被重载了 - 编译器需要检查是否存在这种重载,为此必须知道 lhs 的类型和 rhs 的类型。operator&&