Função passada como argumento de modelo

Eu estou procurando as regras envolvendo passando funções de modelos C ++ como argumentos.

Isso é suportado pelo C ++, conforme mostrado por um exemplo aqui:

#include  void add1(int &v) { v+=1; } void add2(int &v) { v+=2; } template  void doOperation() { int temp=0; T(temp); std::cout << "Result is " << temp << std::endl; } int main() { doOperation(); doOperation(); } 

Aprender sobre essa técnica é difícil, no entanto. Pesquisando por “function como um argumento modelo” não leva muito. E o clássico C ++ Templates The Complete Guide surpreendentemente também não discute (pelo menos não da minha pesquisa).

As perguntas que tenho são se isso é válido C ++ (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 syntax está obviamente errada. Seria bom poder mudar uma function para um functor e vice-versa, semelhante à maneira como você pode passar um ponteiro de function ou functor para o algoritmo std :: sort se você quiser definir uma operação de comparação personalizada.

  struct add3 { void operator() (int &v) {v+=3;} }; ... doOperation(); 

Ponteiros para um link da web ou dois, ou uma página no livro Modelos C ++ seria apreciada!

Sim, é válido.

Quanto a fazê-lo funcionar com os functores, a solução usual é algo assim:

 template  void doOperation(F f) { int temp=0; f(temp); std::cout < < "Result is " << temp << std::endl; } 

que agora pode ser chamado como:

 doOperation(add2); doOperation(add3()); 

O problema com isso é que se torna complicado para o compilador inline a chamada para add2 , já que todo o compilador sabe que um tipo de ponteiro de function void (*)(int &) está sendo passado para doOperation . (Mas add3 , sendo um functor, pode ser facilmente embutido. Aqui, o compilador sabe que um object do tipo add3 é passado para a function, o que significa que a function a ser chamada é add3::operator() , e não apenas alguns desconhecidos. ponteiro de function.)

parameters de modelo podem ser parametrizados por tipo (typename T) ou por value (int X).

A maneira “tradicional” do C ++ de modelar um pedaço de código é usar um functor – ou seja, o código está em um object, e o object, portanto, fornece o tipo exclusivo do código.

Ao trabalhar com funções tradicionais, essa técnica não funciona bem, porque uma mudança no tipo não indica uma function específica – em vez disso, especifica apenas a assinatura de muitas funções possíveis. Assim:

 template int do_op(int a, int b, OP op) { return op(a,b); } int add(int a, int b) { return a + b; } ... int c = do_op(4,5,add); 

Não é equivalente ao caso do functor. Neste exemplo, do_op é instanciado para todos os pointers de function cuja assinatura é int X (int, int). O compilador teria que ser bastante agressivo para integrar totalmente este caso. (Eu não descartaria isso, já que a otimização do compilador ficou bastante avançada).

Uma maneira de dizer que esse código não faz o que queremos é:

 int (* func_ptr)(int, int) = add; int c = do_op(4,5,func_ptr); 

ainda é legal, e claramente isso não está ficando inlined. Para obter o inlining completo, precisamos modelar por valor, portanto, a function está totalmente disponível no modelo.

 typedef int(*binary_int_op)(int, int); // signature for all valid template params template int do_op(int a, int b) { return op(a,b); } int add(int a, int b) { return a + b; } ... int c = do_op(4,5); 

Nesse caso, cada versão instanciada do do_op é instanciada com uma function específica já disponível. Assim, esperamos que o código do do_op se pareça muito com “return a + b”. (Programadores de Lisp, parem de sorrir maliciosamente!)

Podemos também confirmar que isso está mais próximo do que queremos porque isto:

 int (* func_ptr)(int,int) = add; int c = do_op(4,5); 

não irá compilar. O GCC diz: “error: ‘func_ptr’ não pode aparecer em uma expressão constante. Em outras palavras, eu não posso expandir completamente o do_op porque você não me deu informações suficientes no tempo do compilador para saber qual é o nosso op.

Então, se o segundo exemplo está realmente preenchendo nossa op e o primeiro não, qual é o modelo? O que isso está fazendo? A resposta é: digite coerção. Este riff no primeiro exemplo funcionará:

 template int do_op(int a, int b, OP op) { return op(a,b); } float fadd(float a, float b) { return a+b; } ... int c = do_op(4,5,fadd); 

Esse exemplo vai funcionar! (Não estou sugerindo que seja um bom C ++, mas …) O que aconteceu é que o do_op foi modelado em torno das assinaturas das várias funções, e cada instanciação separada escreverá código de coerção de tipo diferente. Então o código instanciado para do_op com o fadd é algo como:

 convert a and b from int to float. call the function ptr op with float a and float b. convert the result back to int and return it. 

Por comparação, nosso caso por valor requer uma correspondência exata nos argumentos da function.

Ponteiros de function podem ser passados ​​como parâmetros de modelo, e isso faz parte do padrão C ++ . No entanto, no modelo, eles são declarados e usados ​​como funções em vez de ponteiro para function. Na instanciação de modelo, um passa o endereço da function em vez de apenas o nome.

Por exemplo:

 int i; void add1(int& i) { i += 1; } template void do_op_fn_ptr_tpl(int& i) { op(i); } i = 0; do_op_fn_ptr_tpl< &add1>(i); 

Se você quiser passar um tipo de functor como um argumento de modelo:

 struct add2_t { void operator()(int& i) { i += 2; } }; template void do_op_fntr_tpl(int& i) { op o; o(i); } i = 0; do_op_fntr_tpl(i); 

Várias respostas passam uma instância de functor como um argumento:

 template void do_op_fntr_arg(int& i, op o) { o(i); } i = 0; add2_t add2; // This has the advantage of looking identical whether // you pass a functor or a free function: do_op_fntr_arg(i, add1); do_op_fntr_arg(i, add2); 

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 de tipo.

 // non-type (function pointer) template parameter template void do_op(int& i) { op(i); } // type (functor class) template parameter template void do_op(int& i) { op o; o(i); } i = 0; do_op< &add1>(i); // still need address-of operator in the function pointer case. do_op(i); 

Honestamente, eu realmente esperava que isso não compilasse, mas funcionou para mim com o gcc-4.8 e o Visual Studio 2013.

Em seu modelo

 template  void doOperation() 

O parâmetro T é um parâmetro de modelo sem tipo. Isso significa que o comportamento da function template muda com o valor do parâmetro (que deve ser corrigido em tempo de compilation, quais constantes de ponteiro de function são).

Se você quiser algo que funcione com os objects de function e parâmetros de function, você precisa de um modelo tipado. Quando você fizer isso, no entanto, você também precisará fornecer uma instância de object (instância de object de function ou um ponteiro de function) para a function em tempo de execução.

 template  void doOperation(T t) { int temp=0; t(temp); std::cout < < "Result is " << temp << std::endl; } 

Existem algumas considerações de desempenho menores. Essa nova versão pode ser menos eficiente com argumentos de ponteiro de function, já que o ponteiro de function específico é somente removido e chamado em tempo de execução, enquanto o modelo de ponteiro de function pode ser otimizado (possivelmente a chamada de function embutida) com base no ponteiro de function específico usado. Objetos de function geralmente podem ser muito eficientemente expandidos com o modelo tipado, embora o operator() particular operator() seja completamente determinado pelo tipo do object de function.

A razão pela qual o seu exemplo functor não funciona é que você precisa de uma instância para invocar o operator() .

Edit: Passando o operador como uma referência não funciona. Para simplificar, entenda como um ponteiro de function. Você acabou de enviar o ponteiro, não uma referência. Eu acho que você está tentando escrever algo assim.

 struct Square { double operator()(double number) { return number * number; } }; template  double integrate(Function f, double a, double b, unsigned int intervals) { double delta = (b - a) / intervals, sum = 0.0; while(a < b) { sum += f(a) * delta; a += delta; } return sum; } 

. .

 std::cout < < "interval : " << i << tab << tab << "intgeration = " << integrate(Square(), 0.0, 1.0, 10) << std::endl;