2010-09-19 10 views
35

Quiero escribir un sumador simple (para risas) que suma cada argumento y devuelve una suma con el tipo apropiado. Actualmente, tengo esto:tipo de retorno final usando decltype con una función de plantilla variadic

#include <iostream> 
using namespace std; 

template <class T> 
T sum(const T& in) 
{ 
    return in; 
} 

template <class T, class... P> 
auto sum(const T& t, const P&... p) -> decltype(t + sum(p...)) 
{ 
    return t + sum(p...); 
} 

int main() 
{ 
    cout << sum(5, 10.0, 22.2) << endl; 
} 

En GCC 4.5.1 esto parece funcionar muy bien para 2 argumentos, por ejemplo, sum (2, 5.5) regresa con 7.5. Sin embargo, con más argumentos que esto, recibo errores de que sum() simplemente no está definido aún. Si Declaro suma() al igual que esto, sin embargo:

template <class T, class P...> 
T sum(const T& t, const P&... p); 

entonces funciona para cualquier número de argumentos, pero suma (2, 5.5) devolvería entero 7, que no es lo que se puede esperar. Con más de dos argumentos supongo que decltype() tendría que hacer algún tipo de recursión para poder deducir el tipo de t + suma (p ...). ¿Es esto legal C++ 0x? o ¿decltype() solo funciona con declaraciones no variadicas? Si ese es el caso, ¿cómo escribirías esa función?

+1

Esta es una interesante problema. Tal vez deberías preguntar en el grupo de Usenet comp.std.C++ si se supone que este tipo de "llamada recursiva" en '-> decltype (expr)' funciona o no. – sellibitze

+3

No se supone que funcione con la redacción actual. El punto de declaración de funciones/variables, etc. es después de su declarador. Por lo tanto, 'sum' en el tipo de devolución especificado más tarde no puede encontrar la plantilla' sum' que se está definiendo. –

+0

@Johannes: ¿Pero la búsqueda no se ha retrasado (hasta la segunda fase) debido a la dependencia de la expresión en los parámetros de la plantilla? – sellibitze

Respuesta

22

creo que el problema es que la plantilla de función variadic sólo se considera declarada después de que especifica el tipo que devuelve de manera que en sumdecltype nunca puede referirse a la plantilla de función variadic en sí. Pero no estoy seguro de si esto es un error de GCC o C++ 0x simplemente no lo permite. Mi conjetura es que C++ 0x no permite una llamada "recursiva" en la parte ->decltype(expr).

Como solución podemos evitar esta llamada "recursivo" en ->decltype(expr) con una clase de rasgos personalizados:

#include <iostream> 
#include <type_traits> 
using namespace std; 

template<class T> typename std::add_rvalue_reference<T>::type val(); 

template<class T> struct id{typedef T type;}; 

template<class T, class... P> struct sum_type; 
template<class T> struct sum_type<T> : id<T> {}; 
template<class T, class U, class... P> struct sum_type<T,U,P...> 
: sum_type< decltype(val<const T&>() + val<const U&>()), P... > {}; 

De esta manera, podemos reemplazar decltype en su programa con typename sum_type<T,P...>::type y se compilará.

Editar: Desde esta realidad devuelve decltype((a+b)+c) en lugar de decltype(a+(b+c)) que sería más cerca de cómo se utiliza, además, se puede sustituir la última especialización con esto: La solución de

template<class T, class U, class... P> struct sum_type<T,U,P...> 
: id<decltype(
     val<T>() 
    + val<typename sum_type<U,P...>::type>() 
)>{}; 
+0

De hecho, esto funciona. No entiendo muy bien la plantilla struct sum_type; aunque. ¿Simplemente usará la plantilla versión? – Maister

+0

@Maister, la primera especialización es para un argumento y la segunda especialización es para al menos dos argumentos (P podría ser un paquete de parámetros vacío). Pero el enfoque de Tomaka17 parece funcionar también. Sin embargo, hay una ligera diferencia. Mi versión te da decltype ((a + b) + c) mientras que la versión de Tomaka17 te da decltype (a + (b + c)). En caso de que trabaje con tipos extraños definidos por el usuario, esto puede marcar la diferencia. – sellibitze

+0

Ya veo, veamos si entendí bien. Entonces, cada vez que se crea una instancia de tipo_tay, plantilla sum_type; se usa, pero dado que hay especializaciones tipo_ suma y tipo_ suma , esas especializaciones se usarán en su lugar, y por lo tanto no hay necesidad de definir realmente el cuerpo de la plantilla estructura tipo_suma; ? – Maister

7

Al parecer, no se puede utilizar decltype de manera recursiva (al menos por el momento, tal vez ellos lo arreglarán)

Se puede utilizar una estructura de plantilla para determinar el tipo de la suma

se ve feo, pero funciona

#include <iostream> 
using namespace std; 


template<typename... T> 
struct TypeOfSum; 

template<typename T> 
struct TypeOfSum<T> { 
    typedef T  type; 
}; 

template<typename T, typename... P> 
struct TypeOfSum<T,P...> { 
    typedef decltype(T() + typename TypeOfSum<P...>::type())  type; 
}; 



template <class T> 
T sum(const T& in) 
{ 
    return in; 
} 

template <class T, class... P> 
typename TypeOfSum<T,P...>::type sum(const T& t, const P&... p) 
{ 
    return t + sum(p...); 
} 

int main() 
{ 
    cout << sum(5, 10.0, 22.2) << endl; 
} 
+0

Para tipos construibles no predeterminados, lo anterior no funciona, hay que reemplazar 'typedef decltype (T() + typename TypeOfSum :: type()) 'by' typedef decltype (std :: declval () + std :: declval :: type>()) 'para evitar problemas –

3

Otra respuesta a la última pregunta con menos de escribir usando C++ 11 de std::common_type: Utilice simplemente

std::common_type<T, P ...>::type 

como tipo de retorno de suma variadic.

En cuanto std::common_type, aquí es un extracto de http://en.cppreference.com/w/cpp/types/common_type:

Para los tipos aritméticas, el tipo común también puede ser visto como el tipo de la (posiblemente de modo mixto) expresión aritmética tal como T0() + T1() + ... + Tn().

Pero obviamente esto funciona solo para expresiones aritméticas y no soluciona el problema general.

+0

Si bien esto podría funcionar, plantea el siguiente problema: suponga que desea calcular la suma sobre plantillas de expresión posiblemente diferentes. Con su enfoque, head + sum (tail ...) no se beneficiará de las optimizaciones que pueden proporcionar las plantillas de expresiones. –

+0

@MartiNito: No entiendo exactamente lo que quieres decir. En primer lugar, el tipo de devolución no implica nada que implique optimizaciones (siempre que sea correcto, es decir, que no implique una conversión costosa o similar). En segundo lugar, 'std :: common_type ' determina el tipo al que se puede convertir implícitamente 'T ...'. Entonces, si puede usarlo en plantillas de expresiones depende de cómo defina las conversiones implícitas en éstas. Pero creo que para las plantillas de expresión, por lo general, se mantendría una determinación explícita del tipo de retorno a través de la recursión de la plantilla (como en las otras respuestas). – davidhigh

+0

Supongamos que trabaja con vectores V matrices M y escalares s. Entonces dentro de él suma a1 + a2 + a3 con a1 = s * V, a2 = M * V y a3 = M.col (1) cada uno de esos sumadores tendrá un tipo diferente (que reflejará la operación subyacente). Es ventajoso retrasar las evaluaciones y realizarlas en el ciclo que se requiere para la suma. Si common_type de a1 y a2 es un vector evaluado, entonces sum (a1 + a2, a3) repetirá dos veces, una para a1 + a2 y una para (a1 + a2) + a3. Por supuesto, la especialización apropiada de common_type para reflejar la plantilla de expresión de la operación a1 + a2 + a3 hará el truco. Hth –

0

Ofrezco esta mejora a la respuesta aceptada. Sólo dos estructuras

#include <utility> 

template <typename P, typename... Ps> 
struct sum_type { 
    using type = decltype(std::declval<P>() + std::declval<typename sum_type<Ps...>::type>()); 
}; 

template <typename P> 
struct sum_type<P> { 
    using type = P; 
}; 

Ahora acaba de declarar sus funciones como

template <class T> 
auto sum(const T& in) -> T 
{ 
    return in; 
} 

template <class P, class ...Ps> 
auto sum(const P& t, const Ps&... ps) -> typename sum_type<P, Ps...>::type 
{ 
    return t + sum(ps...); 
} 

Con esto, su código de prueba ahora trabaja

std::cout << sum(5, 10.0, 22.2, 33, 21.3, 55) << std::endl; 

146,5

Cuestiones relacionadas