c++ - 通过指针与数组引用进行数组索引序列扩展

标签 c++ c++20 libstdc++

我正在查看to_array libstdc++ 的实现 here并注意到他们使用了一个聪明的技巧,通过使用 bool 来避免为函数编写额外的重载。模板参数决定函数是否应将元素移动或复制到新创建的数组。

我决定尝试一下这个技巧并编写了一些测试代码:

template <typename ...P>
void dummy(P...) {}

template <typename T>
int bar(T& ref) {
    printf("Copying %d\n", ref);
    return ref;
}

template <typename T>
int bar(T&& ref) {
    printf("Moving %d\n", ref);
    T oldref = ref;
    ref = 0;
    return oldref;
}

template <bool Move, typename T, std::size_t... I>
void foo(T (&a)[sizeof...(I)], std::index_sequence<I...>) {
    if constexpr (Move) {
        dummy(bar(std::move(a[I]))...);
    } else {
        dummy(bar(a[I])...);
    }
}

template <typename T, std::size_t N>
void baz(T (&a)[N]) {
    foo<false>(a, std::make_index_sequence<N>{});
}

template <typename T, std::size_t N>
void baz(T (&&a)[N]) {
    foo<true>(a, std::make_index_sequence<N>{});
}

在搞乱这个问题时,我偶然发现了我最初认为是编译器中的一个错误,其中更改了 a参数来自T(&a)[...]T(a)[...]产生了相同的汇编代码,但在查看汇编代码中的分解标识符后,我得出结论,它确实不是,只是将调用的签名更改为 foo功能稍微。

例如:

int main() {
    int a1[] = {1, 2, 3, 4};
    baz(a1);
    for (int i = 0; i < 4; i++) {
        printf("%d\n", a1[i]);
    }
    baz(std::move(a1));
    for (int i = 0; i < 4; i++) {
        printf("%d\n", a1[i]);
    }
}

打印

Copying 4
Copying 3
Copying 2
Copying 1
1
2
3
4
Moving 4
Moving 3
Moving 2
Moving 1
0
0
0
0

在这两种情况下都生成了相同的汇编代码,但是当使用 T(&a)[...] 时函数调用看起来像

void foo<false, int, 0ul, 1ul, 2ul, 3ul>(int (&) [4], std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul>)

使用T(a)[...]结果函数调用看起来像

void foo<false, int, 0ul, 1ul, 2ul, 3ul>(int*, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul>)

区别在于第一个参数的签名从对 int 数组的引用更改为指向 int 的指针(也称为 int 数组)。

我使用 clang++11 和 g++11(未优化)测试了代码,结果是一致的。

我的问题是,当两者产生相同的汇编代码并且按预期执行时,为什么您会选择一个选项而不是另一个选项?是否存在它们的行为不同导致 libstdc++ 使用 T(&a) 的情况版本?

Here是我的编译器资源管理器 session 。

最佳答案

相比之下,有很多模板代码涵盖了相当简单的主题。

在以下示例中,您将通过引用传递数组:

#include <iostream>

void baz(int (&arr)[4]) {
    for (const auto e : arr) { std::cout << e << " "; }
}

int main() {
    int a1[] = {1, 2, 3, 4};
    baz(a1);  // 1 2 3 4
    
    return 0;
}

使用特定的T (¶m_name)[SIZE]参数语法。

但是,如果将 bazarr 参数类型修改为 int (arr)[4],则括号不再有任何意义,这相当于 int arr[4] ,即尝试按值传递数组。然而,这是 array to pointer decay ,例如如果我们尝试使用该参数,就好像它实际上是范围可转换的一样,Clang 甚至会给我们一个非常明显的错误消息:

#include <iostream>

void baz(int arr[4]) {
    for (const auto e : arr) { std::cout << e << " "; }
}

int main() {
    int a1[] = {1, 2, 3, 4};
    baz(a1);  // error: cannot build range expression with array
              // function parameter 'arr' since parameter with array 
              // type 'int [4]' is treated as pointer type 'int *'
    
    return 0;
}

事实上,我们可以对您更复杂的示例应用相同的测试:

void foo(T (&a)[sizeof...(I)], std::index_sequence<I...>) {
    for (const auto e : a) { (void)e; }  // Ok!
    // ...
}

void foo(T (a)[sizeof...(I)], std::index_sequence<I...>) {
    for (const auto e : a) { (void)e; }
        // Error: invalid range expression of type 'int *'; 
        // no viable 'begin' function available
    // ...
}

关于c++ - 通过指针与数组引用进行数组索引序列扩展,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62557753/

相关文章:

sfml - 构建 SFML 和 V8

centos - 如何在 centos 中更改/升级我的 GLIBCXX

c++ - 解析字符串并交换子字符串

c++ - Eigen:基类模板特化中的类型推导

c++ - 三向比较运算符的顺序推导不一致

c++ - 将 std::ranges::view 类型对象传递给类的正确方法是什么?

c++ - 没有可用的源 "libstdc++-6!_ZNSo9_M_insertIlEERSoT_() at 0x6fc868a8"

c++ - boost/algorithm/string/trim.hpp 编译错误

c++ - 使用派生类实例化基类而不在对象定义中使用指针

c++ - 如何通过创建类型特征来对 NTTP 类进行分类?