c++ - Especialización de plantillas en plantilla miembro de plantilla clase
templates c++11 (5)
Alternativamente, ¿cuál es la mejor manera (más pequeña / limpia) de hacer que esto funcione?
Podría decirse que es:
- Escriba una plantilla de rasgo SFINAE
Tr<String,Allocator,T>
que determine siT
es lo mismo quebasic_data_object<String,Allocator>::array_container<T::E>
para algún tipoE
, si es que existe , esT::value_type
. - Proporcione la plantilla
get_data_object_value
con un 4to parámetro por defecto aTr<String,Allocator,T>::value
- Escriba especializaciones parciales de
get_data_object_value
instanciando ese 4º parámetro comotrue
,false
respectivamente.
Aquí hay un programa de demostración:
#include <type_traits>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
struct basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
/*
A trait template that has a `static const bool` member `value` equal to
`true` if and only if parameter type `T` is a container type
with `value_type E` s.t.
`T` = `basic_data_object<String,Allocator>::array_container<T::E>`
*/
{
template<typename A>
static constexpr bool
test(std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
> *) {
return std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
>::value;
}
template<typename A>
static constexpr bool test(...) {
return false;
}
static const bool value = test<T>(nullptr);
};
template <
typename String,
template<class> class Allocator,
typename T,
bool Select =
is_basic_data_object_array_container<T,String,Allocator>::value
>
struct get_data_object_value;
template <
typename String,
template<class> class Allocator,
typename T
>
struct get_data_object_value<
String,
Allocator,
T,
false
>
{
static void demo() {
std::cout << "Is NOT a basic_data_object array_container" << std::endl;
}
};
template <
typename String,
template<class> class Allocator,
typename T>
struct get_data_object_value<
String,
Allocator,
T,
true
>
{
static void demo() {
std::cout << "Is a basic_data_object array_container" << std::endl;
}
};
#include <list>
#include <memory>
using namespace std;
int main(int argc, char **argv)
{
get_data_object_value<string,allocator,std::vector<short>>::demo();
get_data_object_value<string,allocator,std::list<short>>::demo();
get_data_object_value<string,allocator,short>::demo();
return 0;
}
Construido con gcc 4.8.2, clang 3.4. Salida:
Is a basic_data_object array_container
Is NOT a basic_data_object array_container
Is NOT a basic_data_object array_container
VC ++ 2013 no compilará esto por falta de soporte de constexpr
. Para acomodar ese compilador, se puede utilizar la siguiente implementación menos natural del rasgo:
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
{
template<typename A>
static
auto test(
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
> *
) ->
std::integral_constant<
bool,
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
>::value
>{}
template<typename A>
static std::false_type test(...);
using type = decltype(test<T>(nullptr));
static const bool value = type::value;
};
Probablemente este es solo un problema de sintaxis.
Así que tengo esta clase de plantilla:
template <typename String, template<class> class Allocator>
class basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
Y otro :
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
};
Ahora quiero especializar el parámetro T
del segundo con el typedef interno array_container
del array_container
para cualquier tipo dado.
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::template array_container<T>>
{
};
Pero esta especialización no parece coincidir cuando paso un std :: vector como el último parámetro.
Si creo un typedef temporal duro codificado:
typedef basic_data_object<std::string, std::allocator<std::string>> data_object;
Y utilízalo para la especialización, todo funciona:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
data_object::template array_container<T>>
{
};
Qué me perdí ? :)
Alternativamente, ¿cuál es la mejor manera (más pequeña / limpia) de hacer que esto funcione?
El estándar de C ++ dice, en [temp.class.spec.match] párrafo 2:
Una especialización parcial coincide con una lista de argumentos de la plantilla real dada si los argumentos de la plantilla de la especialización parcial pueden deducirse de la lista de argumentos de la plantilla real (14.8.2).
14.8.2 es [temp.arg.deduct], es decir, la cláusula que describe la deducción de argumentos de plantilla para plantillas de función.
Si modifica su código para usar una plantilla de función similar e intenta llamarlo, verá que los argumentos no se pueden deducir:
template <typename String, typename T>
void deduction_test(String,
typename basic_data_object<String, std::allocator>::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{});
}
( Allocator
parámetro Allocator
, ya que no hay forma de pasar un parámetro de plantilla de plantilla como un argumento de función y en el tipo basic_data_object
es un contexto no deducido, no creo que afecte el resultado).
Tanto Clang como GCC dicen que no pueden deducir T
aquí. Por lo tanto, la especialización parcial no coincidirá con los mismos tipos utilizados como argumentos de plantilla.
Así que realmente no he respondido la pregunta todavía, solo aclaré que la razón está en las reglas de deducción de argumentos de la plantilla, y mostró una equivalencia con deducción en las plantillas de función.
En 14.8.2.5 [temp.deduct.type] obtenemos una lista de contextos no deducidos que impiden la deducción, y la siguiente regla en el párrafo 6:
Cuando un nombre de tipo se especifica de una manera que incluye un contexto no deducido, todos los tipos que comprenden ese nombre de tipo tampoco se deducen.
Dado que basic_data_object<String, Allocator>
está en un contexto no deducido (es un especificador de nombre anidado , es decir, aparece antes de) ::
eso significa que el tipo T
tampoco es deducido, que es exactamente lo que nos dicen Clang y GCC .
Con su typedef hardcoded temporal no hay un contexto no deducido, por lo que la deducción para T
tiene éxito usando la plantilla de función deduction_test
:
template <typename String, typename T>
void deduction_test(String,
typename data_object::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{}); // OK
}
Y así, correspondientemente, la especialización parcial de su plantilla de clase puede combinarse cuando usa ese tipo.
No veo una manera de hacer que funcione sin cambiar la definición de get_data_object_value
, pero si esa es una opción, puede eliminar la necesidad de deducir el tipo array_container
y, en cambio, usar un rasgo para detectar si un tipo es del tipo que desea, y Especializarse en el resultado del rasgo:
#include <string>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
class basic_data_object
{
public:
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
template<typename T>
struct is_ac : std::false_type { };
template<typename T>
struct is_ac<array_container<T>> : std::true_type { };
};
template <typename String, template<class> class Allocator, typename T, bool = basic_data_object<String, Allocator>::template is_ac<T>::value>
struct get_data_object_value
{
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value<String, Allocator, T, true>
{
void f() { }
};
int main()
{
get_data_object_value<std::string,std::allocator,std::vector<short>> obj;
obj.f();
}
Esto realmente no se puede escalar si quisiera varias especializaciones parciales de plantillas de clase, ya que tendría que agregar varios parámetros de plantillas bool
con argumentos predeterminados.
La respuesta de Jonathan Wakely da la razón por la cual su código no funciona.
Mi respuesta te muestra cómo resolver el problema.
En su ejemplo, el tipo de contenedor en el que desea especializarse se define fuera de basic_data_object
lo que, por supuesto, puede usarlo directamente en su especialización:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,std::vector<T,A>>
{ };
Esto definitivamente se ajusta al estándar y funciona con todos los compiladores.
En el caso de que el tipo esté definido en basic_data_object
, puede moverlo fuera de la clase.
Ejemplo: en lugar de
template<typename S, template<class> class A>
struct a_data_object
{
template<typename T>
struct a_container
{ };
};
escribe esto:
template<typename S, template<class> class A, typename T>
// you can perhaps drop S and A if not needed...
struct a_container
{ };
template<typename S, template<class> class A, typename T>
struct a_data_object
{
// use a_container<S,A,T>
};
Ahora puedes especializarte con:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,a_container<S,A,T>>
{ };
Nota : la siguiente "solución" es aparentemente un error con GCC 4.8.1.
Si el contenedor solo está definido en una plantilla adjunta y no se puede mover, puede hacer esto:
Obtenga el tipo de contenedor de
basic_data_object
:template<typename S, template<class> class A, typename T> using bdo_container = basic_data_object<S,A>::array_container<T>;
Escribe una especialización para este tipo:
template <typename S, template<class> class A, typename T> struct get_data_object_value<S,A,bdo_container<S,A,T>> { };
Por alguna razón, el problema parece provenir del doble nivel de plantillas. Te dejo revisar los 3 casos de prueba a continuación, son simples:
- Eliminar los argumentos de la plantilla de
First
: funciona como se esperaba -
First
haga una plantilla, pero el tipo interno sea sencillo: funciona como se espera - Realice las plantillas
First
y las de tipo interno: compila pero la salida es inesperada
Nota: el parámetro de plantilla de plantilla Allocator
sirve para reproducir el problema, así que lo dejé fuera.
Nota: tanto GCC (versión de ideone, 4.8.1 que creo) como Clang (versión de Coliru, 3.4) compilan el código y producen el mismo resultado desconcertante
De los 3 ejemplos anteriores, deduzco:
-
que esto NO es un problema de contexto no deducible;de lo contrario ¿por qué funcionaría (2)? -
que esto NO es un problema de alias;de lo contrario ¿por qué funcionaría (1)?
Y por lo tanto, ya sea que el problema sea mucho más complejo de lo que los consejos actuales nos harían creer O que tanto gcc como Clang tienen un error.
EDITAR : Gracias a Jonathan Wakely, quien me educó con paciencia lo suficiente para que finalmente pudiera entender tanto la redacción estándar relacionada con este caso como la forma en que se aplicó. Ahora intentaré explicar esto (otra vez) con mis propias palabras. Consulte la respuesta de Jonathan para las citas estándar exactas (todo se encuentra en [temp.deduct.type] )
- Al deducir los parámetros de la plantilla (P i ), ya sea para funciones o clases, la deducción se realiza de forma independiente para todos y cada uno de los argumentos .
- Cada argumento debe proporcionar cero o un candidato C i para cada parámetro; si un argumento proporcionaría más de un candidato , no proporcionaría ninguno en su lugar.
- Por lo tanto, cada argumento produce un diccionario D n : P i -> C i que asigna un subconjunto (posiblemente vacío) de los parámetros de la plantilla que se deducirán a su candidato.
- Los diccionarios D n se fusionan, parámetro por parámetro:
- si solo un diccionario tiene un candidato para un parámetro dado, entonces este parámetro es aceptado, con este candidato
- Si varios diccionarios tienen el mismo candidato para un parámetro dado, entonces este parámetro es aceptado, con este candidato
- Si varios diccionarios tienen diferentes candidatos incompatibles (*) para un parámetro dado, entonces este parámetro es rechazado
- Si el diccionario final está completo (asigna todos y cada uno de los parámetros a un candidato aceptado), la deducción es exitosa, de lo contrario, falla
(*) parece haber una posibilidad de encontrar un "tipo común" entre los candidatos disponibles ... aunque no tiene importancia aquí.
Ahora podemos aplicar esto a los ejemplos anteriores:
1) Existe un único parámetro de plantilla T
:
- patrón coincidente
std::vector<int>
contratypename First::template ArrayType<T>
(que esstd::vector<T>
), obtenemos D 0 :{ T -> int }
- fusionando los únicos rendimientos del diccionario
{ T -> int }
, se deduce queT
esint
2) Existe una única String
parámetros de plantilla.
- patrón coincidente
std::vector<int>
contraString
, obtenemos D 0 :{ String -> std::vector<int> }
- patrón coincidente
std::vector<int>
contratypename First<String>::ArrayType
golpeamos un contexto no deducible (muchos valores deString
podrían caber), obtenemos D 1 :{}
- fusionar los dos diccionarios produce
{ String -> std::vector<int> }
, por lo que se deduce queString
esstd::vector<int>
3) Existen dos parámetros de plantilla String
y T
- patrón coincidente
std::vector<char>
contraString
, obtenemos D 0 :{ String -> std::vector<char> }
- patrón coincidente
std::vector<int>
contratypename First<String>::template ArrayType<T>
golpeamos un contexto no deducible, obtenemos D 1 :{}
- la fusión de los dos diccionarios produce
{ String -> std::vector<char> }
, que es un diccionario incompleto (T
está ausente) falla en la deducción
Debo admitir que aún no había considerado que los argumentos se resolvieron de forma independiente, y por lo tanto, en este último caso, al calcular D 1, el compilador no podía aprovechar el hecho de que D 0 ya había deducido un valor para String
. Sin embargo, la razón por la que se hace de esta manera es probablemente una pregunta completa.
Sin la plantilla exterior, funciona , ya que en ella se imprime "Especializado":
#include <iostream>
#include <vector>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename T>
struct Second {
void go() { std::cout << "General/n"; }
};
template <typename T>
struct Second < typename First::template ArrayType<T> > {
void go() { std::cout << "Specialized/n"; }
};
int main() {
Second < std::vector<int> > second;
second.go();
return 0;
}
Sin la plantilla interna, funciona , ya que imprime "Especializada":
#include <iostream>
#include <vector>
template <typename String>
struct First {
using ArrayType = std::vector<int>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General/n"; }
};
template <typename String>
struct Second < String, typename First<String>::ArrayType > {
void go() { std::cout << "Specialized/n"; }
};
int main() {
Second < std::vector<int>, std::vector<int> > second;
second.go();
return 0;
}
issue , como en el que imprime "General":
#include <iostream>
#include <vector>
template <typename String>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General/n"; }
};
template <typename String, typename T>
struct Second < String, typename First<String>::template ArrayType<T> > {
void go() { std::cout << "Specialized/n"; }
};
int main() {
Second < std::vector<char>, std::vector<int> > second;
second.go();
return 0;
}
Edición : esta respuesta solo funciona debido a un error en GCC 4.8.1
Su código funciona como se esperaba si elimina la template
palabras clave en su especialización:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
void foo() { std::cout << "general" << std::endl; }
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::array_container<T>>
// ^^^^^^ no template!
{
void foo() { std::cout << "special" << std::endl; }
};
Ejemplo probado con GCC 4.8.1:
int main() {
get_data_object_value<std::string,std::allocator,std::vector<int>> obj;
obj.foo(); // prints "special"
}