Search code examples
c++templatesinstantiationtemplate-specialization

Is an entirely new class compiled for each differently sized std::array?


As far as I understand, c++ templating works by compiling a separate class or function for every type needed. This seems logical for classes or functions that will only be called for a handful of different types / parameters, but for std::array it seems like this could result in the same class being compiled into hundreds of different versions.

I understand the advantages of std::array over C style arrays, but it seems like using the former would result in huge binary sizes compared to the latter if my above assumption is correct.

For example, if say in a large program we end up using 99 arrays of different sizes throughout the entire code, so that we effectively have:

int arr[1]   = { ... }
int arr[2]   = { ... }
int arr[...] = { ... }
int arr[99]  = { ... }
int arr[100] = { ... }

Or

 std::array<int, 1> arr   = { ... }
 std::array<int, 2> arr   = { ... }
 std::array<int, ...> arr = { ... }
 std::array<int, 99>  arr = { ... }
 std::array<int, 100> arr = { ... }

Would the std::array example end up with the entire class and all its functions being compiled into the binary 99 times?


Solution

  • Yes, a new class is generated by the class template for each different set of template parameters.

    But that class need not as-if exist in the runtime binary.

    Most of the methods are short, and should be inlined at point of use. So they won't be emitted into the binary.

    If you started taking the address of the methods and storing them, you'd start running into bloat, as you are forcing each distinct method to exist.

    For example of a binary bloat generator:

    template<std::size_t...Ns>
    std::function<std::type_info const&()> stupid(std::size_t i, std::index_sequence<Ns...>) {
      std::function<std::type_info const&()> retval;
      (
        ((i || (retval = []()->std::type_info const&{
           return typeid( std::array<int, Ns> );
        })) && i--) && ...
      );
      return retval;
    }
    std::function<std::type_info const&()> stupid( std::size_t i ) {
      return stupid( i, std::make_index_sequence<100>{} );
    }
    

    this requires that the library contain rtti information for 100 different std::arrays.

    But if you don't do that kind of thing, the rtti isn't needed. So it isn't injected into your binary.

    And I can do the exact same thing with 100 different arrays.

    template<std::size_t...Ns>
    std::function<std::type_info const&()> stupid(std::size_t i, std::index_sequence<Ns...>) {
      std::function<std::type_info const&()> retval;
      (
        ((i || (retval = []()->std::type_info const&{
           return typeid( int[Ns] );
        })) && i--) && ...
      );
      return retval;
    }
    std::function<std::type_info const&()> stupid( std::size_t i ) {
      return stupid( i, std::make_index_sequence<100>{} );
    }
    

    a "class" in C++ isn't a heavy thing like in other OO languages. There is no global class state unless you force it to exist.