Estou procurando as regras que envolvem a passagem de modelos C ++ como argumentos.
Isso é suportado pelo C ++, como mostra um exemplo aqui:
#include <iostream>
void add1(int &v)
{
v+=1;
}
void add2(int &v)
{
v+=2;
}
template <void (*T)(int &)>
void doOperation()
{
int temp=0;
T(temp);
std::cout << "Result is " << temp << std::endl;
}
int main()
{
doOperation<add1>();
doOperation<add2>();
}
Aprender sobre essa técnica é difícil, no entanto. Pesquisar no Google "funcionar como argumento de modelo" não leva a muito. E, surpreendentemente, os clássicos modelos C ++ O Guia Completo também não o discute (pelo menos não na minha pesquisa).
As perguntas que tenho são se este é C ++ válido (ou apenas alguma extensão amplamente suportada).
Além disso, existe uma maneira de permitir que um functor com a mesma assinatura seja usado de forma intercambiável com funções explícitas durante esse tipo de chamada de modelo?
O seguinte não funciona no programa acima, pelo menos no Visual C ++ , porque a sintaxe está obviamente errada. Seria bom poder alternar uma função para um functor e vice-versa, semelhante à maneira como você pode passar um ponteiro de função ou functor para o algoritmo std :: sort, se desejar definir uma operação de comparação personalizada.
struct add3 {
void operator() (int &v) {v+=3;}
};
...
doOperation<add3>();
Ponteiros para um link da Web ou dois, ou uma página no livro Modelos C ++ seriam apreciados!
fonte
-std=gnu++17
. Posso usar o resultado de um operador de conversão constexpr lambda capturável do C ++ 17 como argumento não-tipo de modelo de ponteiro de função? .Respostas:
Sim, é válido.
Quanto a fazê-lo funcionar com functores, a solução usual é algo como isto:
que agora pode ser chamado como:
Veja ao vivo
O problema é que, se for complicado para o compilador incorporar a chamada
add2
, uma vez que tudo o que o compilador sabe é quevoid (*)(int &)
está sendo passado um tipo de ponteiro de funçãodoOperation
. (Masadd3
, sendo um functor, pode ser embutido facilmente. Aqui, o compilador sabe que um objeto do tipoadd3
é passado para a função, o que significa que a função a ser chamada éadd3::operator()
, e não apenas um ponteiro de função desconhecido).fonte
template <typename F> void doOperation(F&& f) {/**/}
), então bind, por exemplo, pode passar uma expressão de bind em vez de vinculá-la?Os parâmetros do modelo podem ser parametrizados por tipo (nome do tipo T) ou por valor (int X).
A maneira "tradicional" de C ++ de modelar um pedaço de código é usar um functor - ou seja, o código está em um objeto e, assim, o objeto fornece ao tipo único de código.
Ao trabalhar com funções tradicionais, essa técnica não funciona bem, porque uma alteração no tipo não indica uma função específica - mas especifica apenas a assinatura de muitas funções possíveis. Assim:
Não é equivalente ao caso do functor. Neste exemplo, do_op é instanciado para todos os ponteiros de função cuja assinatura é int X (int, int). O compilador teria que ser bastante agressivo para integrar totalmente esse caso. (Eu não descartaria isso, pois a otimização do compilador ficou bastante avançada.)
Uma maneira de dizer que esse código não faz exatamente o que queremos é:
ainda é legal e, claramente, isso não está sendo incorporado. Para obter informações completas, precisamos modelar por valor, para que a função esteja totalmente disponível no modelo.
Nesse caso, cada versão instanciada do do_op é instanciada com uma função específica já disponível. Portanto, esperamos que o código para do_op seja muito parecido com "return a + b". (Programadores Lisp, parem de sorrir!)
Também podemos confirmar que isso está mais próximo do que queremos, porque isso:
falhará na compilação. O GCC diz: "error: 'func_ptr' não pode aparecer em uma expressão constante. Em outras palavras, não consigo expandir completamente do_op porque você não me forneceu informações suficientes no tempo do compilador para saber qual é a nossa operação.
Então, se o segundo exemplo está realmente alinhando totalmente a nossa operação, e o primeiro não, qual é o bom modelo? O que isso está fazendo? A resposta é: digite coerção. Este riff no primeiro exemplo funcionará:
Esse exemplo vai funcionar! (Não estou sugerindo que seja bom C ++, mas ...) O que aconteceu é que do_op foi modelado em torno das assinaturas das várias funções, e cada instanciação separada escreverá um código de coerção de tipo diferente. Portanto, o código instanciado para do_op com fadd se parece com:
Por comparação, nosso caso por valor requer uma correspondência exata nos argumentos da função.
fonte
int c = do_op(4,5,func_ptr);
"claramente não está sendo incorporada".Os ponteiros de função podem ser passados como parâmetros de modelo, e isso faz parte do C ++ padrão . No entanto, no modelo, eles são declarados e usados como funções, em vez de ponteiro para função. Na instanciação do modelo, passa-se o endereço da função em vez de apenas o nome.
Por exemplo:
Se você deseja passar um tipo de functor como um argumento de modelo:
Várias respostas passam uma instância de functor como argumento:
O mais próximo que você pode chegar dessa aparência uniforme com um argumento de modelo é definir
do_op
duas vezes - uma vez com um parâmetro não-tipo e uma vez com um parâmetro-tipo.Honestamente, eu realmente esperava que isso não fosse compilado, mas funcionou para mim com o gcc-4.8 e o Visual Studio 2013.
fonte
No seu modelo
O parâmetro
T
é um parâmetro de modelo não-tipo. Isso significa que o comportamento da função do modelo muda com o valor do parâmetro (que deve ser corrigido no tempo de compilação, quais são as constantes do ponteiro da função).Se você deseja algo que funcione com objetos de função e parâmetros de função, precisa de um modelo digitado. Ao fazer isso, no entanto, você também precisa fornecer uma instância de objeto (instância de objeto de função ou um ponteiro de função) para a função em tempo de execução.
Existem algumas considerações menores sobre desempenho. Essa nova versão pode ser menos eficiente com argumentos de ponteiro de função, pois o ponteiro de função específico é apenas desprotegido e chamado em tempo de execução, enquanto o modelo de ponteiro de função pode ser otimizado (possivelmente a chamada de função incorporada) com base no ponteiro de função específico usado. Os objetos de função geralmente podem ser expandidos de maneira muito eficiente com o modelo digitado, embora o particular
operator()
seja completamente determinado pelo tipo do objeto de função.fonte
A razão pela qual seu exemplo de functor não funciona é que você precisa de uma instância para chamar o
operator()
.fonte
Editar: Passar o operador como referência não funciona. Para simplificar, entenda-o como um ponteiro de função. Você acabou de enviar o ponteiro, não uma referência. Eu acho que você está tentando escrever algo assim.
. .
fonte