2012-04-16 5 views
5

Si tengo un objeto a que o una matriz incorporada o un tipo de clase con un operator [] adecuado, y su tipo devuelto se puede indexar, ¿cómo debería escribir una función genérica que pueda indexarlos todos con una variadic llamada en lugar de bloque de soporte separado? En otras palabras, puedo hacer expresiones como:¿Cómo se puede hacer una función de indexación encadenada basada en variantes?

a[i0][i1]...[iK] 

y quiero ser capaz de escribir como una única función:

slice(a, i0, i1, ..., iK) 

ya que las reglas de C++ requieren operator [] a trabajar en argumentos individuales , haciéndolo menos compatible con cosas variadas. (Esta pregunta se basa en un hilo de Usenet, donde he intentado preguntar algo similar; terminando la solución sólo posiblemente anidados-matrices incorporadas en mí mismo.)

Un primer intento:

template < typename T, typename U > 
constexpr 
auto slice(T &&t, U &&u) noexcept(???) -> ??? 
{ return ce_forward<T>(t)[ ce_forward<U>(u) ]; } 

template < typename T, typename U, typename V, typename ...W > 
constexpr 
auto slice(T &&t, U &&u, V &&v, W... &&w) noexcept(???) -> ??? 
{ 
    return slice(ce_forward<T>(t)[ce_forward<U>(u)], ce_forward<V>(v), 
    ce_forward<W>(w)...); 
} 

Los ce_forward plantillas de función son a constexpr -marked std::forward. (Algunas cosas en el estándar que podrían marcarse constexpr no lo son). Estoy tratando de encontrar las cosas correctas para poner en el tipo de devolución y en los puntos de especificación de excepción. Algunos casos y advertencias que conozco son:

  • El incorporada en operator [] requiere un operando para ser un puntero de datos (o decaído referencia de matriz) y el otro para ser un tipo de enumeración o entero. Los operandos pueden estar en cualquier orden. No sé si un operando podría reemplazarse por un tipo de clase con una conversión inequívoca (¿no explícita?) A un tipo apropiado.
  • Un tipo de clase puede definir operator [] como funciones miembro no estáticas. Cualquier función de este tipo solo puede tener un parámetro (además de this).
  • El operador incorporado no puede tirar. (A menos que los operandos se basen en una conversión UDT, dicha conversión es el único punto de lanzamiento posible). El comportamiento indefinido causado por los errores de límites está más allá de nuestro alcance aquí.
  • Si la última llamada de indexación devuelve una referencia de valor l, la referencia de valor r o el valor debe reflejarse en el tipo de devolución de slice.
  • Si la llamada (final?) Es por valor, entonces std::is_nothrow_move_constructible<ReturnType>::value se debe OR a la especificación de excepción. (Los retornos por referencia son noexcept).
  • Si el operador incorporado implica una matriz, la referencia devuelta para ese paso debe ser una referencia de valor r si la matriz también lo es. (Esto es un tipo de defecto porque los punteros, a diferencia de las matrices, pierden el estado del valor l- vs r del objetivo, por lo que la devolución tradicional siempre sería una referencia de valor l)
  • Para operadores de indexación de clase, asegúrese de que las secciones de excepción-especificación y de tipo de devolución se refieran a la misma sobrecarga (si hay más de una) que utiliza el cuerpo de la función. Tenga cuidado con las sobrecargas que solo difieren en la calificación de this (const/volatile/ambos/ninguno y/o &/&&/ninguno).
+0

Debe ser 'constexpr'? – kennytm

+0

Para lo que lo estoy usando, debe ser 'constexpr' siempre que sea posible. – CTMacUser

+1

¿Ha considerado simplemente poner la misma expresión que en la declaración 'return' para el tipo de retorno y la especificación' noexcept'? P.ej. 'noexcept (noexcept (std :: declval () [std :: declval ()])) -> decltype (std :: declval () [std :: declval ()])'. –

Respuesta

3

He encontrado una solución, basada en el comentario de @ luc-danton y respuesta por @ user315052.

#include <utility> 

template < typename Base, typename ...Indices > 
class indexing_result; 

template < typename T > 
class indexing_result<T> 
{ 
public: 
    using type = T; 

    static constexpr 
    bool can_throw = false; 
}; 

template < typename T, typename U, typename ...V > 
class indexing_result<T, U, V...> 
{ 
    using direct_type = decltype(std::declval<T>()[std::declval<U>()]); 
    using next_type = indexing_result<direct_type, V...>; 

    static constexpr 
    bool direct_can_throw 
    = not noexcept(std::declval<T>()[std::declval<U>()]); 

public: 
    using type = typename next_type::type; 

    static constexpr 
    bool can_throw = direct_can_throw || next_type::can_throw; 
}; 

template < typename T > 
inline constexpr 
auto slice(T &&t) noexcept -> T && 
{ return static_cast<T &&>(t); } 

template < typename T, typename U, typename ...V > 
inline constexpr 
auto slice(T &&t, U &&u, V &&...v) 
    noexcept(!indexing_result<T, U, V...>::can_throw) 
    -> typename indexing_result<T, U, V...>::type 
{ 
    return slice(static_cast<T &&>(t)[static_cast<U &&>(u)], 
    static_cast<V &&>(v)...); 
} 

pongo un full example program como un Gist. Funcionó en un compilador de sitio web ejecutando GCC> = 4.7, CLang> = 3.2 e Intel C++> = 13.0.

+0

'bool direct_can_throw = no noexcept (...)' se supone que 'no' está ahí? – 0x499602D2

+0

@ 0x499602D2, sí. El elemento 'noexcept' quiere saber cuándo lo hacemos * no * tirar, pero mi clase calcula cuándo tirar. – CTMacUser

+0

Oh, sí, olvidé que la palabra clave 'no' era lo mismo que'! ':) – 0x499602D2

1

Para obtener el valor de retorno correcto para la función slice, puede crear una plantilla auxiliar que calcule el tipo de resultado correcto.Por ejemplo:

template <typename T, typename U, typename... V> 
struct SliceResult { 
    typedef typename SliceResult<T, V...>::Type Type; 
}; 

template <typename T, typename U> 
struct SliceResult<T, U> { 
    typedef typename std::underlying_type<T[U(0)]>::type Type; 
}; 

La función slice entonces devolver un SliceResult<...>::Type.

Cuestiones relacionadas