Como especifico um ponteiro para uma função sobrecarregada?

137

Eu quero passar uma função sobrecarregada para o std::for_each()algoritmo. Por exemplo,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

Eu esperaria que o compilador resolvesse f()pelo tipo de iterador. Aparentemente, ele (GCC 4.1.2) não faz isso. Então, como posso especificar qual f()eu quero?

davka
fonte

Respostas:

137

Você pode usar static_cast<>()para especificar qual fusar de acordo com a assinatura da função implícita no tipo de ponteiro de função:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

Ou, você também pode fazer isso:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

Se ffor uma função membro, você precisará usar mem_fun, ou para o seu caso, a solução apresentada neste artigo do Dr. Dobb .

Em sílico
fonte
1
obrigado! Eu ainda tenho um problema, embora, provavelmente devido ao fato de que f()é um membro de uma classe (veja o exemplo editado acima)
davka
9
@the_drow: O segundo método é realmente muito mais seguro, se uma das sobrecargas desaparecer, o primeiro método silenciosamente oferece um comportamento indefinido, enquanto o segundo pega o problema no momento da compilação.
Ben Voigt
3
@BenVoigt Hmm, testei isso no vs2010 e não consegui encontrar um caso em que o static_cast não detectasse o problema no momento da compilação. Ele forneceu um C2440 com "Nenhuma das funções com esse nome no escopo corresponde ao tipo de destino". Você pode esclarecer?
Nathan Monteleone
5
@ Nathan: É possível que eu estivesse pensando reinterpret_cast. Na maioria das vezes, vejo elencos em estilo C usados ​​para isso. Minha regra é que os lançamentos nos ponteiros de função são perigosos e desnecessários (como mostra o segundo trecho de código, existe uma conversão implícita).
Ben Voigt 23/05
3
Para funções de membro:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w
29

Lambdas para o resgate! (observação: C ++ 11 obrigatório)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

Ou usando decltype para o parâmetro lambda:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

Com lambdas polimórficas (C ++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

Ou desambique removendo a sobrecarga (funciona apenas para funções gratuitas):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}
milleniumbug
fonte
Viva para lambdas! De fato, uma excelente solução para o problema de resolução de sobrecarga. (. Pensei nisso também, mas decidiu deixá-lo fora da minha resposta, para não turvar as águas)
aldo
Mais código para o mesmo resultado. Eu acho que não foi para isso que as lambdas foram feitas.
Tomáš Zato - Restabelece Monica
@ TomášZato A diferença é que essa resposta funciona, e a resposta aceita não (por exemplo, postado pelo OP - você também precisa usar mem_fne bind, que BTW. Também é C ++ 11). Além disso, se queremos ser realmente pedantes, [&](char a){ return f(a); }são 28 caracteres e static_cast<void (A::*)(char)>(&f)35 caracteres.
precisa
1
@ TomášZato Lá você vai coliru.stacked-crooked.com/a/1faad53c4de6c233 não sabe como deixar isso mais claro #
milleniumbug
18

Por que isso não funciona?

Eu esperaria que o compilador resolvesse f()pelo tipo de iterador. Aparentemente, (gcc 4.1.2) não faz isso.

Seria ótimo se fosse esse o caso! No entanto, for_eaché um modelo de função, declarado como:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

A dedução de modelo precisa selecionar um tipo para UnaryFunctionno ponto da chamada. Mas fnão tem um tipo específico - é uma função sobrecarregada, existem muitos fs com tipos diferentes. Atualmente, não existe uma maneira de for_eachauxiliar o processo de dedução de modelo, indicando o que fele deseja; portanto, a dedução de modelo simplesmente falha. Para que a dedução do modelo seja bem-sucedida, você precisa trabalhar mais no site de chamada.

Solução genérica para corrigi-lo

Entrando aqui alguns anos e C ++ 14 depois. Em vez de usar a static_cast(que permitiria a dedução do modelo com êxito "corrigindo" o que fqueremos usar, mas requer que você execute manualmente a resolução de sobrecarga para "corrigir" a correta)), queremos fazer o compilador funcionar para nós. Queremos chamar falguns argumentos. Da maneira mais genérica possível, é isso:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

É muito difícil digitar, mas esse tipo de problema surge irritantemente com frequência, para que possamos apenas envolvê-lo em uma macro (suspiro):

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

e então apenas use:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

Isso fará exatamente o que você deseja que o compilador faça - execute a resolução de sobrecarga no fpróprio nome e faça a coisa certa. Isso funcionará independentemente de fser uma função livre ou uma função de membro.

Barry
fonte
7

Não para responder à sua pergunta, mas eu sou o único que acha

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

mais simples e mais curto que a for_eachalternativa sugerida por in silico neste caso?

anon
fonte
2
provavelmente, mas é chato :) também, se eu quiser usar o iterador para evitar o operador [], isso se tornará mais longo ...
davka
3
@Davka Boring é o que queremos. Além disso, os iteradores geralmente não são mais rápidos (podem ser mais lentos) do que usar op [, se essa for sua preocupação.
7
Os algoritmos devem ser preferidos aos loops, pois são menos propensos a erros e podem ter melhores oportunidades de otimização. Há um artigo sobre isso em algum lugar ... aqui está: drdobbs.com/184401446
AshleysBrain
5
@ Ashley Até que eu veja algumas estatísticas objetivas sobre o "menos propenso a erros", não vejo necessidade de acreditar. E Meyers no artigo parece falar sobre loops que usam iteradores - estou falando sobre a eficiência de loops que NÃO usam iteradores - meus próprios benchmarks tendem a sugerir que esses são marginalmente mais rápidos quando otimizados - certamente não mais lentos.
1
Aqui estou eu, também acho sua solução muito melhor.
peterh - Restabelece Monica
5

O problema aqui parece não ser a resolução de sobrecarga, mas na verdade a dedução dos parâmetros do modelo . Embora a excelente resposta do @In silico resolva um problema ambíguo de sobrecarga em geral, parece que a melhor solução ao lidar com std::for_each(ou similar) é especificar explicitamente seus parâmetros de modelo :

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}
aldo
fonte
4

Se você não se importa em usar o C ++ 11, aqui está um ajudante inteligente que é semelhante (mas menos feio que) ao elenco estático:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(Funciona para funções-membro; deve ser óbvio como modificá-lo para funcionar em funções independentes, e você deve poder fornecer as duas versões e o compilador selecionará a correta para você.)

Agradecemos a Miro Knejp por sugerir: consulte também https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .

Mateus
fonte
O problema do OP não é conseguir passar um nome sobrecarregado para um modelo de função, e sua solução envolve passar um nome sobrecarregado para um modelo de função? Este é exatamente o mesmo problema.
Barry
1
@ Barry Não é o mesmo problema. A dedução de argumento do modelo é bem-sucedida nesse caso. Funciona (com alguns pequenos ajustes).
Oktalist 12/08/16
@ Oktalist Porque você está fornecendo R, não é deduzido. Também não há menção a isso nesta resposta.
Barry
1
@ Barry Não estou fornecendo R, estou fornecendo Args. Re Tsão deduzidos. É verdade que a resposta poderia ser melhorada. (Não existe Tno meu exemplo, porém, porque não é um membro do ponteiro-to-, porque isso não iria funcionar com std::for_each.)
Oktalist