(talvez o título já se enquadre no problema XY...)
Dada esta função modelada:
template <typename T, typename U, typename V, typename W>
void MyFunc()
{
// stuff...
}
Gostaria de chamá-lo para todas as combinações dadas de tipos. Por exemplo, gostaria de chamar para:
- T: char, int, longo longo
- U: flutuar, duplo
- V: std::uint64_t, std::string_view
- W: int, MeuEnum1, MeuEnum2, MeuEnum3
Em pseudocódigo, ficaria assim:
foreach (t in { char, int, long long })
{
foreach (u in { float, double })
{
foreach (v in { std::uint64_t, std::string_view })
{
foreach (w in { int, MyEnum1, MyEnum2, MyEnum3 })
{
MyFunc<t, u, v, w>();
}
}
}
}
Como posso conseguir isso?
Acredito que truques/padrões de modelos variáveis, como tuplas ou "listas de tipos", são o caminho a seguir, mas não consegui resolvê-los.
Não sei se esta é a maneira mais elegante, mas pelo menos está funcionando:
Minha versão tem apenas 3 listas de argumentos variáveis, mas deve ser fácil estender a ideia para qualquer número fixo de listas de tipos.
A ideia central é obter múltiplas listas de argumentos variádicos capturando-os em uma struct que por si só tem argumentos de template variádicos. Isso permite passar múltiplas listas em uma struct/função.
E então usei structs em vez de funções para fazer uso da especialização parcial, que permite que você acesse os argumentos variáveis que estão compactados no
TypeContainer
.O que você precisa é fazer o produto cartesiano dos tipos, o que pode ser feito facilmente usando
boost::mp11
:Aqui está um método que funciona para um número arbitrário de listas de tipos usando apenas recursos do C++20 e bibliotecas padrão. As listas de tipos são expressas com um aninhado
std::tuple
.Em
loopForTypesHelper()
, o arraysizes
contém os tamanhos para as listas de tipos para loop. No caso do OP, o arraysizes
contém os elementos3, 2, 2, 4
.As próximas linhas usam o algoritmo padrão para obter os produtos acumulativos dos elementos, começando pelos últimos elementos, e armazenam o resultado em um
constexpr array
nomeadocumprod
. Um elemento extra1
é anexado ao final para facilitar a codificação posterior. No caso do OP, o arraycumprod
contém o array48, 16, 8, 4, 1
.A declaração-chave para expandir os tipos é a seguinte:
Aqui,
Idx
está um número que faz um loop de 0 acumprod[0]-1
(veja abaixo), Para cadaIdx
, a expressãoIdx/cumprod[ArrayIdx+1]%sizes[ArrayIdx]
indica qual tipo devemos escolher daArrayIdx
-ésima lista de tipos naIdx
-ésima iteração. O primeiro (interno)std::tuple_element_t
escolhe o tipo da lista de tipos correspondente enquanto o segundo (externo)std::tuple_element_t
escolhe uma lista de tipos da lista de listas de tipos. O final...
expande oArrayIdx
para coletar os tipos escolhidos de cada lista de tipos e passá-los como argumentos de modelo paramyFunc
.Depois de definir isso,
usa o
std::make_index_sequence
para realmente executar o loop forIdx
from0
tocumprod[0]-1
.loopForTypes
é simplesmente um wrapperloopForTypesHelper
que expande os tamanhos de cada lista de tipos como argumentos de modelo.Isso pode ser aplicado a um número arbitrário de listas de tipos e pode ser reescrito de modo que, em vez de codificar rigidamente,
myFunc
seja possível passar qualquer functor com um templatedoperator()
. O único limite é que o número total de iterações (cumprod[0]
) é restrito pelos limites máximos de expansão de template definidos pela implementação.Demonstração: https://godbolt.org/z/dP43hM4qT
Você tem outra possibilidade usando
std::views::cartesian_product
para produzir todas as combinações possíveis de índices como um constexprstd::array
Então você pode chamar a função necessária para cada combinação:
Você pode reunir tudo isso em uma
Executor
struct e usá-la assim:Uma demonstração completa aqui .
Atualizar
É possível tornar a solução um pouco mais genérica usando uma
sequence
função que recebe como entrada uma lista de inteiros e chama um lambda com cada combinação de índices possívelcb
do produto cartesiano. Ele também fornece um pacote de inteiros que podem ser usados para expressões de dobra, por exemplo. Se alguém fornece apenas uma dimensão parasequence
, então o pacote de inteiros é a única informação fornecida e o lambda tem permissão para retornar algum valor.A
main
função se tornaria:Demonstração