【问题标题】:Why does the std::size on an array passed by value do not work?为什么按值传递的数组上的 std::size 不起作用?
【发布时间】:2026-02-03 21:00:01
【问题描述】:

为什么std::size() 不适用于按值传递的静态分配数组?

void print_elemTab(int tab[])
{
   // ...
   int size = std::size(tab); //error 
   // ...
}

void test_tab()
{
   const int    TAB_SIZE = 5;
   int          tab[TAB_SIZE] = {};
   // ...
   cout << std::size(tab) << std::endl; //print 5
   print_elemTab(tab);
   // ...
}

我正在打印尺寸,然后在子函数print_elemTab() 中传递tab,我再次使用std::size()

我得到没有匹配函数错误,所以我想知道为什么std::size() 第一次在test_tab() 中起作用,而不是在print_elemTab()

我必须通过引用传递它吗?那么除了任意长度的数组,我该怎么做呢?

或者我是否因为我不知道的事情而不得不以另一种方式做到这一点?

【问题讨论】:

  • 在函数参数列表中,int tab[] 的含义与int *tab 完全相同。所以你传递的是一个指针而不是一个数组。
  • 或者更改为void print_elemTab(int tab[], std::size_t n) 并拨打print_elemTab(tab, std::size(tab))

标签: c++ arrays c++17 pass-by-reference


【解决方案1】:

表达式中使用的数组指示符被隐式转换(极少数例外,例如在sizeof 运算符中使用它们)为指向它们的第一个元素的指针。

所以在这次通话中

print_elemTab(tab);

参数表达式的类型为int *

另一方面,具有数组类型的函数参数被编译器调整为指向数组元素类型的指针。

例如这些函数声明

void     print_elemTab(int tab[]);
void     print_elemTab(int tab[5]);
void     print_elemTab(int tab[100]);

声明同一个函数,等价于下面的声明

void     print_elemTab(int *tab);

尽管编译器会发出一条消息表明存在冗余声明,但您甚至不能在程序中包含所有这些声明。

因此,在函数中,您正在处理int * 类型的指针。而sizeof( int * ) 通常等于48,具体取决于所使用的系统。

如果您有这样的声明,您应该更改它,指定第二个参数,该参数将保留传递数组中的元素数量,如

void     print_elemTab(int *tab, size_t n );

并且函数可以像这样调用

print_elemTab(tab, std::size( tab ) );

另一种方法是通过引用传递数组。在这种情况下,您应该声明一个模板函数,例如

template <size_t N>
void     print_elemTab( int ( &tab )[N] );

在函数中,您可以直接使用模板参数N 作为数组中元素的数量。或者您可以将相同的标准 C++ 函数 std::size 应用于数组。

或者可以使用第二个模板类型参数来更一般地声明函数,例如

template <typename T, size_t N>
void     print_elemTab( T ( &tab )[N] );

另一种方法是像这样声明函数

template <typename Container>
void     print_elemTab( Container &container );

在这种情况下,您也可以将标准函数std::size 应用于参数容器。

【讨论】:

    【解决方案2】:

    我必须通过引用传递它吗?所以我怎么做,但对于任何 任意长度的数组?

    是的,通过引用传递它是一种选择。

    template<std::size_t n>
    void print_elemTab(int (&tab)[N]) // const int (&tab)[N], if the elements won't be modified
    {
        std::cout << N << "\n"; // where you can directly get the size `N`
    }
    

    或者像下面这样简单的模板化函数

    template<typename T>
    void  print_elemTab(T& tab)// const T& tab, if the elements won't be modified
    {
       const auto size = std::size(tab);
       std::cout << size << "\n";
    }
    

    另一种选择是将数组推断为其实际类型。在您的情况下,tab 的类型为 int[5]。如果您perfectly forward 通过模板函数,编译器可以推断出它的实际类型(而不是衰减到指针)。

    #include <iostream>
    #include <array>
    
    template<typename T>
    void  print_elemTab(T&& tab)
    {
       const auto size = std::size(tab);  // now you can do std::size() on the int[size]
       std::cout << size << "\n";
    }
    

    【讨论】:

      【解决方案3】:

      是的,你必须通过引用传递它,因为它在传递给你的函数时会衰减为一个指针。为了使函数接受任何大小,我建议使用如下函数模板

      #include <iostream>
      
      template<size_t n>
      void print_elemTab(int (&tab)[n])
      {
          int size = std::size(tab);
          std::cout << size << "\n";// or just  std::cout << n; and ignore the previous line 
      }
          
      void test_tab() {
          const int TAB_SIZE = 5;
          int tab[TAB_SIZE] = {};
          std::cout << std::size(tab) << std::endl; //print 5
          print_elemTab(tab);
          
      }
          
      int main(){
          test_tab();
      }
      
       
      

      【讨论】:

      • 可能想提一下,模板函数是一系列函数,而不是适用于所有数组的单个函数 - 如果代码调用 print_elemTab() 并带有五个 int 的数组,并且在其他地方,具有三个int 的数组,将调用两个不同的函数(为其生成代码)。这可能会导致更大的可执行文件,除非编译器内联做得很好(并非所有编译器都这样做)。不是炫耀,而是使用此技术时需要注意的事项。此外,在您的示例中,使用 std::size(tab) 是不必要的,因为它始终等于 n
      • @Peter 感谢您的评论我已经更新了答案,但我想 OP 已经知道模板。
      最近更新 更多