2012-04-03 16 views
41

Cuando defino esta función,C++ 11 no deduce tipo cuando las funciones std :: función o lambda están involucrados

template<class A> 
set<A> test(const set<A>& input) { 
    return input; 
} 

I puede llamar usando test(mySet) en otra parte en el código sin tener que definir explícitamente la tipo de plantilla Sin embargo, cuando se utiliza la siguiente función:

template<class A> 
set<A> filter(const set<A>& input,function<bool(A)> compare) { 
    set<A> ret; 
    for(auto it = input.begin(); it != input.end(); it++) { 
     if(compare(*it)) { 
      ret.insert(*it); 
     } 
    } 
    return ret; 
} 

Cuando llamo a esta función utilizando filter(mySet,[](int i) { return i%2==0; }); me sale el siguiente error:

error: no matching function for call to ‘filter(std::set&, main()::)’

Sin embargo, todas estas versiones hacer trabajo:

std::function<bool(int)> func = [](int i) { return i%2 ==0; }; 
set<int> myNewSet = filter(mySet,func); 

set<int> myNewSet = filter<int>(mySet,[](int i) { return i%2==0; }); 

set<int> myNewSet = filter(mySet,function<bool(int)>([](int i){return i%2==0;})); 

¿Por qué C++ 11 no puede adivinar el tipo de plantilla cuando puse la función lambda directl y dentro de la expresión sin crear directamente un std::function?

EDIT:

por consejo de Luc Danton en los comentarios, aquí es una alternativa a la función que tenía anteriormente que no necesita las plantillas para pasar de forma explícita.

template<class A,class CompareFunction> 
set<A> filter(const set<A>& input,CompareFunction compare) { 
    set<A> ret; 
    for(auto it = input.begin(); it != input.end(); it++) { 
     if(compare(*it)) { 
      ret.insert(*it); 
     } 
    } 
    return ret; 
} 

Esto se puede llamar set<int> result = filter(myIntSet,[](int i) { i % 2 == 0; }); sin necesidad de la plantilla.

El compilador puede incluso adivinar los tipos de devolución hasta cierto punto, utilizando la nueva palabra clave decltype y utilizando la nueva función tipografía de devolución de tipo. Este es un ejemplo que convierte un conjunto de un mapa, utilizando una función de filtrado y una función que genera las claves en base a los valores:

template<class Value,class CompareType,class IndexType> 
auto filter(const set<Value>& input,CompareType compare,IndexType index) -> map<decltype(index(*(input.begin()))),Value> { 
    map<decltype(index(*(input.begin()))),Value> ret; 
    for(auto it = input.begin(); it != input.end(); it++) { 
     if(compare(*it)) { 
      ret[index(*it)] = *it; 
     } 
    } 
    return ret; 
} 

También puede ser llamado sin usar directamente la plantilla, como

map<string,int> s = filter(myIntSet,[](int i) { return i%2==0; },[](int i) { return toString(i); }); 
+5

No relacionado con su pregunta, pero se da cuenta de que su 'filtro' es esencialmente equivalente a una versión no genérica de' std :: copy_if', ¿no? –

+0

Ah, no estaba al tanto de std :: copy_if, gracias por señalarlo. Sin embargo, esto es parte de un grupo más grande de 4 funciones, una que convierte set => map durante el filtrado y no veo una forma de implementar eso con copy_if y permitir que el usuario produzca claves usando los valores en el conjunto. Por consistencia en el uso, estoy optando por hacerlo de esta manera. – Datalore

Respuesta

50

El problema está en la naturaleza de las lambdas. Son objetos funcionales con un conjunto fijo de propiedades según el estándar, pero son y no una función. El estándar determina que lambdas se puede convertir a std::function<> con los tipos exactos de argumentos y, si no tienen estado, funcionan con punteros.

Pero eso no significa que una lambda sea std::function ni un puntero a la función. Son tipos únicos que implementan operator().

La deducción de tipo, por otro lado, solo deducirá los tipos exactos, sin conversiones (que no sean const/volatilidades). Como el lambda no es un std::function, el compilador no puede deducir el tipo en la llamada: filter(mySet,[](int i) { return i%2==0; }); para que sea cualquier instanciación std::function<>.

Como en los otros ejemplos, en el primero convierte el lambda al tipo de función, y luego lo pasa. El compilador puede deducir el tipo allí, como en el tercer ejemplo donde std::function es un valor r (temporal) del mismo tipo.

Si proporciona el tipo de creación de instancias int a la plantilla, segundo ejemplo de trabajo, la deducción no entra en juego, el compilador usará el tipo y luego convertirá la lambda al tipo apropiado.

+11

Tenga en cuenta que no es el lambda el que realiza la conversión a 'std :: function', es' std :: function' que acepta todo lo que se puede llamar. – Xeo

+0

La deducción de tipo también hará que las "conversiones" de tipo base se completen. – Yakk

+3

Se puede hacer que funcione inhabilitando la deducción para el 'compare' arg.Podemos hacer esto con esta firma: 'plantilla conjunto filtro (const establece y entrada , nombretipo NoOp > :: Tipo de comparar )' ', donde Noop' se define como' plantilla NoOp struct { typedef tipo T; }; ' –

7

Olvídate de tu caso. ya que eso es demasiado complejo para el análisis.

Tome este sencillo ejemplo:

template<typename T> 
struct X 
{ 
    X(T data) {} 
}; 

template<typename T> 
void f(X<T> x) {} 

Ahora llamar f como:

f(10); 

Aquí puede verse tentado a pensar que T se deducirá a inty, por lo tanto, la función anterior la llamada debería funcionar. Bueno, ese no es el caso. Para mantener cosa simple, imagine que hay otra constructor que toma como int:

template<typename T> 
struct X 
{ 
    X(T data) {} 
    X(int data) {} //another constructor 
}; 

Ahora lo T se derivarán a, cuando escribo f(10)? Bueno, T podría tipo.

Tenga en cuenta que podría haber muchos otros casos similares. Tome esta especialización, por ejemplo:

template<typename T> 
struct X<T*>   //specialized for pointers 
{ 
    X(int data) {}; 
}; 

Ahora lo T se derivarán a la llamada f(10)? Ahora parece aún más difícil.

Por lo tanto, es contexto no deducible, lo que explica por qué su código no funciona para std::function que es un caso idéntico — simplemente se ve complejo en la superficie. Tenga en cuenta que lambda no son de tipo std::function — son básicamente las instancias de clases generadas compilador (es decir que están funtores de diferentes tipos que std::function).

Cuestiones relacionadas