How do I conditionally declare one or more arrays based on template value?

110 Views Asked by At

At compile time, I need to allocate memory by declaring up to three separate arrays, each allocated separately from three equally sized memory banks. Starting from the first bank, the array is declared up to the maximum bank size. The remainder memory is allocated from the next bank and so on. The banks are not adjacent in memory.

As an example, assume we need to allocate 2.5K of memory, each bank has a maximum size of 1K. We allocate 1K in Bank 1, another 1K in Bank 2, and with the remaining 500 bytes in Bank 3:

std::array<uint8_t, 1000> bank1; // 1K
std::array<uint8_t, 1000> bank2; // 1K
std::array<uint8_t, 500> bank3; // 500 bytes

If there's no memory left to allocate in the subsequent banks, it doesn't declare an array. If there is, it just fails.

So far, I am having difficulty generating the declarations and the branching.

// Define the maximum bank size (1K in this example)
constexpr std::size_t MaxBankSize = 1000;

// Helper function to calculate the remainder size
template <std::size_t MemSize, std::size_t BankSize>
constexpr std::size_t CalculateRemainder() {
    return (MemSize > BankSize) ? (MemSize - BankSize) : 0;
}

// Bank1: Allocate up to MaxBankSize
template <std::size_t MemSize>
using Bank1 = std::enable_if_t<(MemSize <= MaxBankSize), std::array<uint8_t, MemSize>>;

// Bank2: Allocate from Bank1 and calculate remainder
template <std::size_t MemSize>
using Bank2 = std::conditional_t<(MemSize > MaxBankSize),
    Bank1<MaxBankSize>,
    Bank1<MemSize>>;

// Bank3: Allocate from Bank2 and calculate remainder
template <std::size_t MemSize>
using Bank3 = std::conditional_t<(MemSize > MaxBankSize * 2),
    Bank2<MaxBankSize>,
    Bank2<MemSize>>;
1

There are 1 best solutions below

6
tbxfreeware On

This answer gets the correct size for each bank, but does not cause an empty bank to vanish due to SFINAE. When a bank is empty, it is still declared, but with a size of zero.

All tests were made with MaxBankSize set to 1000.

Although the arrays can be declared with "global" (i.e., file-level or namespace) scope, for the tests here, they are given "local" (i.e., block) scope.

Note: when the arrays are local, you can use N_Banks<MemSize> with constexpr-if to prevent the unwanted banks from being declared.

// main.cpp
#include <array>
#include <cstddef>
#include <iostream>

// Define the maximum bank size (1K in this example)
constexpr std::size_t MaxBankSize = 1000;

constexpr std::size_t zero{};
constexpr std::size_t one{ 1u };
constexpr std::size_t two{ 2u };
constexpr std::size_t three{ 3u };

template< std::size_t MemSize >
constexpr std::size_t N_FullBanks
    = MemSize >= MaxBankSize 
    ? MemSize / MaxBankSize 
    : zero;

template< std::size_t MemSize >
constexpr std::size_t N_PartialBanks
    = MemSize >= MaxBankSize
    ? (MemSize % MaxBankSize ? one : zero)
    : one;

template< std::size_t MemSize >
constexpr std::size_t N_Banks
    = N_FullBanks<MemSize> + N_PartialBanks<MemSize>;

template< std::size_t MemSize >
constexpr std::size_t Size_PartialBank 
    = MemSize >= MaxBankSize
    ? MemSize - N_FullBanks<MemSize> * MaxBankSize
    : MemSize;

template< std::size_t MemSize >
constexpr std::size_t Size_Bank1 
    = N_FullBanks<MemSize> >= one 
    ? MaxBankSize 
    : Size_PartialBank<MemSize>;

template< std::size_t MemSize >
constexpr std::size_t Size_Bank2 
    = MemSize < MaxBankSize ? zero
    : N_FullBanks<MemSize> >= two ? MaxBankSize 
    : N_FullBanks<MemSize> == one ? Size_PartialBank<MemSize>
    : zero;

template< std::size_t MemSize >
constexpr std::size_t Size_Bank3 
    = MemSize < MaxBankSize ? zero
    : N_FullBanks<MemSize> >= three ? MaxBankSize 
    : N_FullBanks<MemSize> == two ? Size_PartialBank<MemSize>
    : zero;

template< std::size_t MemSize >
void test()
{
    std::array<std::uint8_t, Size_Bank1<MemSize>> bank1;
    std::array<std::uint8_t, Size_Bank2<MemSize>> bank2;
    std::array<std::uint8_t, Size_Bank3<MemSize>> bank3;

    std::cout
        << "MemSize: " << MemSize
        << "\n  bank1.size(): " << bank1.size()
        << "\n  bank2.size(): " << bank2.size()
        << "\n  bank3.size(): " << bank3.size()
        << "\n"
        << "\n  N_Banks<MemSize>        : " << N_Banks<MemSize>
        << "\n  N_FullBanks<MemSize>    : " << N_FullBanks<MemSize>
        << "\n  N_PartialBanks<MemSize> : " << N_PartialBanks<MemSize>
        << "\n\n";
}
int main()
{
    test<500>();
    test<1000>();
    test<1500>();
    test<2500>();
    test<3500>();
}
// end file: main.cpp

Output:

MemSize: 500
  bank1.size(): 500
  bank2.size(): 0
  bank3.size(): 0

  N_Banks<MemSize>        : 1
  N_FullBanks<MemSize>    : 0
  N_PartialBanks<MemSize> : 1

MemSize: 1000
  bank1.size(): 1000
  bank2.size(): 0
  bank3.size(): 0

  N_Banks<MemSize>        : 1
  N_FullBanks<MemSize>    : 1
  N_PartialBanks<MemSize> : 0

MemSize: 1500
  bank1.size(): 1000
  bank2.size(): 500
  bank3.size(): 0

  N_Banks<MemSize>        : 2
  N_FullBanks<MemSize>    : 1
  N_PartialBanks<MemSize> : 1

MemSize: 2500
  bank1.size(): 1000
  bank2.size(): 1000
  bank3.size(): 500

  N_Banks<MemSize>        : 3
  N_FullBanks<MemSize>    : 2
  N_PartialBanks<MemSize> : 1

MemSize: 3500
  bank1.size(): 1000
  bank2.size(): 1000
  bank3.size(): 1000

  N_Banks<MemSize>        : 4
  N_FullBanks<MemSize>    : 3
  N_PartialBanks<MemSize> : 1