Estou tentando armazenar em um std::tuple
número variável de valores, que mais tarde serão usados como argumentos para uma chamada para um ponteiro de função que corresponda aos tipos armazenados.
Eu criei um exemplo simplificado mostrando o problema que estou tentando resolver:
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
void delayed_dispatch() {
// How can I "unpack" params to call func?
func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
// But I *really* don't want to write 20 versions of dispatch so I'd rather
// write something like:
func(params...); // Not legal
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
Normalmente, para problemas envolvendo std::tuple
ou modelos variados, eu escreveria outro modelo template <typename Head, typename ...Tail>
para avaliar recursivamente todos os tipos, um por um, mas não vejo uma maneira de fazer isso para despachar uma chamada de função.
A verdadeira motivação para isso é um pouco mais complexa e, na maioria das vezes, é apenas um exercício de aprendizado. Você pode supor que recebi a tupla por contrato de outra interface, portanto não pode ser alterada, mas o desejo de descompactá-la em uma chamada de função é meu. Isso exclui o uso de std::bind
uma maneira barata de contornar o problema subjacente.
Qual é uma maneira limpa de despachar a chamada usando o std::tuple
, ou uma maneira melhor alternativa de alcançar o mesmo resultado líquido de armazenar / encaminhar alguns valores e um ponteiro de função até um ponto futuro arbitrário?
auto saved = std::bind(f, a, b, c);
... e depois apenas ligarsaved()
?Respostas:
Você precisa criar um pacote de parâmetros com números e descompactá-los
fonte
struct gens
definição genérica (aquela que herda de uma derivação expandida do mesmo dito). Vejo que, eventualmente, atinge a especialização com 0. Se o humor lhe convier e você tiver ciclos de reposição, se puder expandir isso e como é utilizado para isso, ficaria eternamente grato. E eu gostaria de poder votar isso cem vezes. Eu me diverti mais brincando com tangentes desse código. Obrigado.seq<0, 1, .., N-1>
. Como funciona:gens<5>: gens<4, 4>: gens<3, 3, 4>: gens<2, 2, 3, 4> : gens<1, 1, 2, 3, 4> : gens<0, 0, 1, 2, 3, 4>
. O último tipo é especializado, criandoseq<0, 1, 2, 3, 4>
. Truque bastante inteligente.gens
:template <int N, int... S> struct gens { typedef typename gens<N-1, N-1, S...>::type type; };
std::integer_sequence<T, N>
e sua especialização parastd::size_t
,std::index_sequence<N>
- além de suas funções auxiliares associadasstd::make_in(teger|dex)_sequence<>()
estd::index_sequence_for<Ts...>()
. E em C ++ 17, há uma série de outras coisas boas integrados na biblioteca - particularmente incluindostd::apply
estd::make_from_tuple
, o que iria lidar com a descompactação e chamando pedaçosA solução C ++ 17 é simplesmente para usar
std::apply
:Achei que isso deveria ser declarado uma vez em uma resposta neste tópico (depois que ele já apareceu em um dos comentários).
A solução básica do C ++ 14 ainda está faltando neste segmento. EDIT: Não, na verdade, está lá na resposta de Walter.
Esta função é dada:
Chame-o com o seguinte trecho:
Exemplo:
DEMO
fonte
http://coliru.stacked-crooked.com/a/8ea8bcc878efc3cb
std::make_unique
diretamente? Precisa de instância de função concreta? 2. Por questd::move(ts)...
se podemos mudar[](auto... ts)
para[](auto&&... ts)
?std::make_unique
espera uma tupla, e uma tupla pode ser criada a partir de uma tupla descompactada apenas através de outra chamada parastd::make_tuple
. Isso é o que eu fiz no lambda (embora seja altamente redundante, pois você também pode simplesmente copiar a tupla no ponteiro exclusivo sem qualquer usocall
).Esta é uma versão compilável completa da solução de Johannes para a pergunta de awoodland, na esperança de que possa ser útil a alguém. Isso foi testado com um instantâneo do g ++ 4.7 no squeeze do Debian.
Pode-se usar o seguinte arquivo SConstruct
Na minha máquina, isso dá
fonte
Aqui está uma solução C ++ 14.
Isso ainda precisa de uma função auxiliar (
call_func
). Como esse é um idioma comum, talvez o padrão deva apoiá-lo diretamente, comostd::call
na possível implementaçãoEntão nosso atraso no envio se torna
fonte
std::call
. O zoológico caóticointeger_sequence
e osindex_sequence
tipos de auxiliares do C ++ 14 são explicados aqui: en.cppreference.com/w/cpp/utility/integer_sequence Observe a ausência conspícua destd::make_index_sequence(Args...)
, e é por isso que Walter foi forçado a entrar na sintaxe mais pesadastd::index_sequence_for<Args...>{}
.Isso é um pouco complicado de conseguir (mesmo que seja possível). Eu aconselho você a usar uma biblioteca onde este já está implementado, ou seja, Boost.Fusion (a invocação de função). Como bônus, o Boost Fusion também funciona com os compiladores C ++ 03.
fonte
c ++ 14solução. Primeiro, alguns clichês utilitários:
Isso permite que você chame um lambda com uma série de números inteiros em tempo de compilação.
e nós terminamos.
index_upto
eindex_over
permite que você trabalhe com pacotes de parâmetros sem precisar gerar uma nova sobrecarga externa.Claro, em c ++ 17 você apenas
Agora, se gostamos disso, em c ++ 14 nós podemos escrever:
de forma relativamente fácil e obtenha o limpador c ++ 17 sintaxe pronta para envio.
apenas substitua
notstd
porstd
quando seu compilador atualiza e bob é seu tio.fonte
std::apply
<- música para os meus ouvidosindex_upto
e menos flexível. ;) Tente chamarfunc
com os argumentos para trás comindex_upto
estd::apply
respectivamente. É certo que quem diabos quer invocar uma função de uma tupla para trás.std::tuple_size_v
é C ++ 17, de modo que para a C ++ 14 solução que teria de ser substituído portypename std::tuple_size<foo>::value
value
não seja um tipo. Mas consertado de qualquer maneira.sizeof...(Types)
. Eu gosto da sua solução sem otypename
.Pensando no problema, um pouco mais com base na resposta dada, encontrei outra maneira de resolver o mesmo problema:
O que requer alterar a implementação de
delayed_dispatch()
para:Isso funciona convertendo recursivamente o
std::tuple
em um pacote de parâmetros por si só.call_or_recurse
é necessário como uma especialização para finalizar a recursão com a chamada real, que apenas descompacta o pacote de parâmetros concluído.Não tenho certeza se essa é uma solução "melhor", mas é outra maneira de pensar e resolvê-la.
Como outra solução alternativa, você pode usar
enable_if
, para formar algo sem dúvida mais simples do que minha solução anterior:A primeira sobrecarga pega apenas mais um argumento da tupla e o coloca em um pacote de parâmetros. A segunda sobrecarga pega um pacote de parâmetros correspondente e, em seguida, faz a chamada real, com a primeira sobrecarga sendo desativada no primeiro caso em que a segunda seria viável.
fonte
Minha variação da solução de Johannes usando o C ++ 14 std :: index_sequence (e o tipo de retorno de função como parâmetro de modelo RetT):
fonte