Não é o argumento de modelo (a assinatura) de std :: function parte de seu tipo?

Dado o seguinte código, qual é a razão por trás da ambigüidade? Posso contornar isso ou terei que manter os castings explícitos (irritantes)?

#include  using namespace std; int a(const function& f) { return f(); } int a(const function& f) { return f(0); } int x() { return 22; } int y(int) { return 44; } int main() { a(x); // Call is ambiguous. a(y); // Call is ambiguous. a((function)x); // Works. a((function)y); // Works. return 0; } 

Curiosamente, se eu comentar a function a() com a function parâmetro e chamar a(x) no meu principal, a compilation corretamente falha por causa da incompatibilidade de tipo entre x a function argumento function da única function a() disponível. Se o compilador falhar nesse caso, por que haveria alguma ambiguidade quando as duas funções a() estiverem presentes?

Eu tentei com VS2010 e g ++ v. 4.5. Ambos me dão a mesma ambiguidade.

    O problema é que ambas as function e function são construtíveis a partir da mesma function. Isto é o que a declaração de construtor de std::function parece no VS2010:

     template function(_Fx _Func, typename _Not_integral< !_Is_integral<_Fx>::value, int>::_Type = 0); 

    Ignorando a parte SFINAE, é construível a partir de praticamente qualquer coisa.
    std::/boost::function emprega uma técnica chamada tipo erasure , para permitir que objects / funções arbitrárias sejam passadas, desde que satisfaçam a assinatura quando forem chamadas. Uma desvantagem disso é que você obtém um erro na parte mais profunda da implementação (onde a function salva está sendo chamada) ao fornecer um object que não pode ser chamado como a assinatura deseja, em vez de no construtor.


    O problema pode ser ilustrado com esta pequena class:

     template class myfunc{ public: template myfunc(Func a_func){ // ... } }; 

    Agora, quando o compilador procura por funções válidas para o conjunto de sobrecargas, ele tenta converter os argumentos se nenhuma function de ajuste perfeita existir. A conversão pode acontecer através do construtor do parâmetro da function, ou através de um operador de conversão do argumento dado à function. No nosso caso, é o primeiro.
    O compilador tenta a primeira sobrecarga de a . Para torná-lo viável, é necessário fazer uma conversão. Para converter um int(*)() para um myfunc , ele tenta o construtor de myfunc . Sendo um modelo que leva tudo, a conversão naturalmente é bem-sucedida.
    Agora ele tenta o mesmo com a segunda sobrecarga. O construtor continua sendo o mesmo e ainda recebe qualquer coisa, a conversão também funciona.
    Sendo deixado com 2 funções no conjunto de sobrecarga, o compilador é um panda triste e não sabe o que fazer, então simplesmente diz que a chamada é ambígua.


    Portanto, no final, a parte Signature do modelo pertence ao tipo ao fazer declarações / definições, mas não quando você deseja construir um object.


    Editar :
    Com toda a minha atenção em responder a questão do título, eu esqueci completamente sua segunda pergunta. 🙁

    Posso contornar isso ou terei que manter os castings explícitos (irritantes)?

    Afaik, você tem 3 opções.

    • Mantenha o casting
    • Faça um object de function do tipo apropriado e passe

      function fx = x; function fy = y; a(fx); a(fy);

    • Ocultar o casting entediante em uma function e usar o TMP para obter a assinatura correta

    A versão do TMP (template metaprogramming) é bastante detalhada e com código clichê, mas oculta a conversão do cliente. Uma versão de exemplo pode ser encontrada aqui , que se baseia na get_signature get_signature que é parcialmente especializada nos tipos de ponteiro de function (e fornece um bom exemplo de como a correspondência de padrões pode funcionar em C ++):

     template struct get_signature; template struct get_signature{ typedef R type(); }; template struct get_signature{ typedef R type(A1); }; 

    Claro, isso precisa ser estendido para o número de argumentos que você deseja suportar, mas isso é feito uma vez e depois enterrado em um header "get_signature.h" . 🙂

    Outra opção que considero, mas descartada imediatamente, foi a SFINAE, que introduziria ainda mais código clichê do que a versão TMP.

    Então, sim, são as opções que eu conheço. Espero que um deles funcione para você. 🙂

    Eu vi essa pergunta aparecer muitas vezes. A libc ++ agora compila este código sem ambigüidade (como uma extensão em conformidade).

    Atualização atrasada

    Essa “extensão” provou ser suficientemente popular e padronizada em C ++ 14 (embora eu não fosse pessoalmente responsável por fazer esse trabalho).

    Em retrospecto, não obtive essa extensão exatamente correta. No início deste mês (2015-05-09) o comitê votou na edição 2420 do LWG que efetivamente altera a definição de Callable para que, se a std::function tiver um tipo void return, ela ignore o tipo de retorno do functor envolvido, mas ainda assim de outra forma, considere Callável se tudo o mais corresponder, em vez de considerá-lo não Callable .

    Esse ajuste pós-C ++ 14 não afeta esse exemplo específico, já que os tipos de retorno envolvidos são consistentemente int .

    Aqui está um exemplo de como quebrar std::function em uma class que verifica a invokability de seus parâmetros de construtor:

     template struct check_function; template struct check_function: public std::function { template::value || std::is_convertible< decltype(std::declval()(std::declval()...)), R>::value>::type> check_function(T &&t): std::function(std::forward(t)) { } }; 

    Use assim:

     int a(check_function f) { return f(); } int a(check_function f) { return f(0); } int x() { return 22; } int y(int) { return 44; } int main() { a(x); a(y); } 

    Observe que isso não é exatamente o mesmo que sobrecarregar a assinatura de function, pois trata os tipos convertíveis de argumento (e retorno) como equivalentes. Para sobrecarga exata, isso deve funcionar:

     template struct check_function_exact; template struct check_function_exact: public std::function { template::value>::type> check_function_exact(T &&t): std::function(std::forward(t)) { } }; 

    std::function tem um ctor de conversão que toma um tipo arbitrário (ou seja, algo diferente de um T ). Claro, nesse caso, esse ctor resultaria em um erro de incompatibilidade de tipos, mas o compilador não chega tão longe – a chamada é ambígua simplesmente porque o ctor existe.