compilar - Cómo separar la definición de la declaración para una plantilla de clase usando ''extern'' en C++ 11 en una biblioteca(dll, entonces,..)
g++ windows (6)
Aquí hay una cita de la norma:
Para un conjunto dado de argumentos de plantilla, si aparece una creación de instancias explícita de una plantilla después de una declaración de una especialización explícita para esa plantilla, la creación de instancias explícita no tiene efecto. [...]
Así que su declaración de instanciación explícita
extern template EXPORTDEF class PropertyHelper<float>;
No tiene efecto ya que aparece después de la especialización explícita.
template<>
class EXPORTDEF PropertyHelper<float>
...
Lo mismo con su definición de instanciación explícita
template class PropertyHelper<float>;
en el archivo cpp (asumo que el archivo cpp incluye class.h, ya que de lo contrario el código no se compilaría).
¿Está seguro de que desea especializar explícitamente PropertyHelper<float>
?
Soy un desarrollador de una biblioteca de código abierto. Una de nuestras clases está preparada para un montón de tipos diferentes. Actualmente, la definición reside en el archivo de encabezado, que tiene un efecto negativo en los tiempos de compilación y también obliga a los usuarios a incluir más encabezados de los necesarios. Mi objetivo es el siguiente:
Para reducir el tiempo de compilación, quiero hacer uso de la declaración de creación de instancias explícita introducida con C ++ 11.
La definición de los métodos y miembros de la clase, que son todos estáticos, debe estar separada de la declaración en un archivo de implementación. Deben estar disponibles para su uso dentro y fuera de la biblioteca sin que los usuarios tengan que hacer una definición de creación de instancias explícita ni nada de esto.
Esto debe ejecutarse multiplataforma en todos los compiladores comunes que admiten C ++ 11 (Visual Studio 2013+, GCC, etc.)
C ++ 11 proporciona nuevas funciones para las plantillas de clase, en concreto la " declaración de creación de instancias explícita ". Por lo que entiendo, esto puede ser usado en este contexto. Las preguntas anteriores trataron esto en contextos similares, p. Ej., Cómo usar la plantilla externa y la definición / creación de instancias de las plantillas de plantilla sin ''extern'', pero esas no se ocupan de las exportaciones de la biblioteca y su solución causa errores de vinculador si un cliente intenta usar la biblioteca compartida .
Actualmente, he logrado implementar esto de una manera que compila, vincula y ejecuta en Visual Studio 2015, pero no estoy seguro si uso las palabras clave correctamente, especialmente la __declspec en este caso. Esto es lo que obtuve (simplificado):
// class.h
template<typename T>
class PropertyHelper;
template<typename T>
class PropertyHelper<const T>
{
public:
typedef typename PropertyHelper<T>::return_type return_type;
static inline return_type fromString(const String& str)
{
return PropertyHelper<T>::fromString(str);
}
static const int SomeValue;
};
template<>
class EXPORTDEF PropertyHelper<float>
{
public:
typedef float return_type;
static return_type fromString(const String& str);
static const int SomeValue;
};
extern template EXPORTDEF class PropertyHelper<float>;
La última línea es la declaración de instanciación explícita. Según tengo entendido, esto significa que los clientes no tienen que declarar esto por sí mismos cada vez. EXPORTDEF
es una definición que es __declspec (dllexport) o __declspec (dllimport) en Windows. No estoy seguro de si necesito colocar esto en la línea anterior, porque lo siguiente también compila, vincula y ejecuta:
extern template class PropertyHelper<float>;
El archivo cpp se ve así:
const int PropertyHelper<float>::SomeValue(12);
PropertyHelper<float>::return_type
PropertyHelper<float>::fromString(const String& str)
{
float val = 0.0f;
if (str.empty())
return val;
//Some code here...
return val;
}
template class PropertyHelper<float>;
La última línea es la definición de instanciación explícita.
Por lo tanto, mi pregunta es sobre todo si hice todo correctamente aquí de acuerdo con el estándar C ++ 11 y, en segundo lugar (si primero es cierto) si la palabra clave __declspec es redundante en el contexto de la declaración de creación de instancias explícita, o sobre lo que debería hacer al respecto Es así como no encontré la información adecuada en los documentos de MSDN.
De la definicion del idioma
A.12 Plantillas [gram.temp]
En la parte inferior encontramos las reglas en cuestión (disculpe el formato):
§ A.12 1224c ISO / IEC N4527
explicit-instantiation:
extern opt template declaration
explicit-specialization:
template < > declaration
Así que diría que la respuesta a la segunda pregunta es que no debería necesitar __declspec para la declaración extern
.
En cuanto a la primera pregunta, estoy de acuerdo con Daveed V. más arriba en que, según el estándar, las declaraciones explícitas parecen ser extrañas. [Ver: 14.8.1 1 especificación de argumento de plantilla explícita [temp.arg.explicit] de la anterior]
Debe reenviar declarar los usos de la clase de plantilla con el enlace adecuado (declspec importar / exportar para Visual Studio o ''extern'' para todo lo demás) para que el compilador no intente generar código para esos tipos importados.
Los detalles específicos se encuentran en mi respuesta a: La especialización de plantillas de C ++ en diferentes dll produce errores de vinculador
clase.h
#ifdef _WIN32
# define TEMPLATE_EXTERN
# ifdef EXPORT
# define LIB_EXPORT __declspec(dllexport)
# else
# define LIB_EXPORT __declspec(dllimport)
# endif
#else
# define TEMPLATE_EXTERN extern
# define LIB_EXPORT
#endif
class PropertyHelper<const T>
{
public:
typedef typename PropertyHelper<T>::return_type return_type;
static inline return_type fromString(const String& str)
{
return PropertyHelper<T>::fromString(str);
}
static const int SomeValue;
};
// forward declare the specialization
TEMPLATE_EXTERN template class LIB_EXPORT PropertyHelper<float>;
class.cpp
// define the symbol to turn on exporting
#define EXPORT
#include "class.h"
// explicitly instantiate the specialization
template class PropertyHelper<float>
test.cpp
#include "class.h"
int main() {
PropertyHelper<float> floatHelper; // should be imported, class.h was not #include''ed with EXPORT defined
return 0;
}
El estándar (desde el borrador de trabajo de C ++ 0x hasta el borrador de trabajo en 2014), en la sección 14.7.2 describe la creación de instancias explícita, indicando que hay dos formas de creación de instancias explícita, definición y declaración. Dice: "una declaración de creación de instancias explícita comienza con la palabra clave externa". Además, especifica que las declaraciones, utilizando extern, no generan código.
Se debe tener cuidado de garantizar que las declaraciones se emitan dentro del espacio de nombres de la declaración de clase de plantilla, o específicamente hacer referencia al espacio de nombres en el nombre calificado, como en:
namespace N {
template< class T > void f( T& ) {}
}
template void N::f<int>(int &);
Instalar una función de plantilla y generar su código (definición). Mientras:
extern template void N::f<int>(int &);
Crea una función de plantilla para el tipo int como una declaración, pero no genera código. La palabra clave externa informa al compilador que el código se proporcionará en el momento del enlace desde otra fuente (posiblemente una biblioteca dinámica, pero el estándar no discute ese concepto específico de la plataforma).
Además, es posible crear una instancia de los miembros y las funciones de los miembros de forma selectiva, como en:
namespace N {
template<class T> class Y { void mf() { } };
}
template void N::Y<double>::mf();
Esto genera el código solo para la función mf (), para dobles. Por lo tanto, es posible declarar instancias (utilizando extern) y luego definir instancias (sin extern) para partes específicas de un tipo de plantilla. Uno podría elegir generar código o miembros para algunas partes de una clase de plantilla dentro de cada unidad de compilación (en línea), y forzar la generación de otras partes de código en una unidad de compilación o biblioteca en particular.
El artículo del Centro de conocimiento de IBM para su compilador XLC V 11.1, compatible con el borrador C ++ 0x, describe la estrategia para usar la palabra clave externa al crear bibliotecas. Por su ejemplo, y los proyectos de documentos de normas a lo largo de varios años (que han sido consistentes a partir de 2008 sobre este tema), está claro que extern tiene una aplicabilidad limitada a las características específicas de las bibliotecas dinámicas, pero en general se limita a controlar dónde se encuentra el código generado. metido. Aún se requerirá que el autor se adhiera a las demandas específicas de la plataforma en relación con el enlace dinámico (y la carga). Eso está más allá del propósito de la palabra clave externa.
Extern es igualmente aplicable a bibliotecas estáticas o bibliotecas dinámicas, pero la limitación en el diseño de una biblioteca es importante.
Decir que una declaración de clase de plantilla, presentada en un archivo de encabezado, existe como:
namespace N
{
template< typename T >
class Y
{
private:
int x;
T v;
public:
void f1( T & );
void f2( T &, int );
};
}
A continuación, en un archivo CPP:
namespace N
{
template< typename T> void Y<T>::f1( T & ) { .... }
template< typename T> void Y<T>::f2( T &, int ) { .... }
}
Ahora considere los usos potenciales de Y. Los consumidores de la biblioteca solo pueden requerir ejemplificaciones de Y para int, float y double. Todos los otros usos no tendrían ningún valor. Este es un punto de diseño del autor de la biblioteca, no una idea general sobre este concepto. Por alguna razón, el autor solo admite esos tres tipos para T.
Para ello, se pueden incluir declaraciones de ejemplificación explícita en el archivo de encabezado.
extern template class N::Y< int >;
extern template class N::Y< float >;
extern template class N::Y< double >;
Como esto se procesa por las distintas unidades de compilación del usuario, se informa al compilador que se generará un código para estos tres tipos, pero el código no se genera en cada unidad de compilación a medida que el usuario construye. De hecho, si el autor no incluye el archivo CPP que define las funciones f1 y f2 para la clase de plantilla Y, el usuario no podrá usar la biblioteca.
Suponiendo que, por el momento, una biblioteca estática es el producto previsto para la clase de plantilla Y (para simplificar esta discusión), el autor compila la biblioteca estática con las funciones de definición de CPP f1 y f2, junto con las definiciones explícitas de creación de instancias:
template class N::Y< int >;
template class N::Y< float >;
template class N::Y< double >;
Esto hará que el código se genere para la clase de plantilla Y en nombre de estos tres tipos, creando una biblioteca estática. El código de usuario ahora tendrá que vincular a esta biblioteca, pero no hacer nada más para usar las clases. Sus unidades de compilación no generarán código para la clase de plantilla Y, en lugar de eso, incorporarán ese código de la biblioteca.
El mismo concepto se aplica a una biblioteca dinámica, pero las especificaciones de la plataforma con respecto a las declaraciones de funciones, la carga dinámica y la vinculación dinámica no se encuentran en los estándares para los borradores de trabajo de C ++ a 2014, con respecto a C ++ 0x, C ++ 11 o C ++ 14. en el presente. La palabra clave externa en las instancias explícitas de la plantilla está limitada a la creación de declaraciones, su ausencia crea definiciones (donde se genera el código).
Esto hace surgir la pregunta con respecto a los usuarios de una biblioteca de este tipo que intenta utilizar Y durante largos sin signo, char o algún otro tipo no proporcionado en una biblioteca dinámica o estática. El autor tiene la opción de negarse a admitir esto al no distribuir el origen para la generación de código (las definiciones de función de f1 y f2 para la clase de plantilla Y). Sin embargo, si el autor deseaba admitir dicho uso, distribuyendo esa fuente, se requerirían instrucciones para que el usuario genere una nueva biblioteca para reemplazar la existente, o genere una segunda biblioteca para los tipos adicionales.
En cualquier caso, sería conveniente separar las definiciones de ejemplificación explícita en un archivo CPP que incluya el encabezado que declara la clase de plantilla Y, que incluye un encabezado de las definiciones de funciones de f1 y f2 para la clase de plantilla Y (en oposición a la práctica de incluyendo un archivo CPP, que también podría funcionar). De esta manera, el usuario crearía un archivo CPP que incluya el encabezado para la clase de plantilla Y, luego las definiciones de función para la clase de plantilla Y, luego emitir las nuevas definiciones de creación de instancias explícitas:
#include "ydeclaration.h" // the declaration of template class Y
#include "ydefinition.h" // the definition of template class Y functions (like a CPP)
template class N::Y< unsigned long >;
template class N::Y< char >;
Para una biblioteca estática, poco más se requeriría, y el usuario podría optar por construir la unidad de compilación adicional dentro de su proyecto, obviando la necesidad de un objetivo de biblioteca estática.
Sin embargo, si el usuario deseara construir una biblioteca dinámica, sería necesario tener cuidado con el código específico de la plataforma con respecto a las bibliotecas dinámicas en una plataforma en particular. Específicamente en Windows, por ejemplo, esto puede significar cargar explícitamente la nueva biblioteca dinámica.
Teniendo en cuenta las complicaciones relacionadas con la creación de bibliotecas dinámicas, es una maravilla que alguien lo haga. A veces simplemente no hay otra opción. La clave para la decisión es determinar exactamente por qué se debe usar una biblioteca dinámica. En la antigua época de las computadoras con menos de 1 GByte de RAM, una de las justificaciones era ahorrar memoria compartiendo código, pero para cualquier biblioteca en particular, ¿cuál es la probabilidad de que compartir código dé como resultado el ahorro de RAM? Para algo tan común como el tiempo de ejecución de C, o el DLL de Windows MFC, puede ser muy probable. Las bibliotecas que proporcionan servicios altamente específicos, por otra parte, tienen más probabilidades de ser utilizadas por un solo programa en ejecución.
Un propósito realmente bueno es el concepto de comportamientos de conexión. Los navegadores, IDE, software de edición de fotos, software de CAD y otros se benefician de una industria completa de aplicaciones distribuidas como complementos a productos existentes, que se distribuyen como bibliotecas dinámicas.
Otra justificación es la distribución de actualizaciones. Si bien esta es una teoría atractiva, la práctica puede causar más problemas de los que vale la pena.
Otra justificación común es la "modularidad". ¿Con qué fin, sin embargo? Separar unidades de compilación ya reduce los tiempos de compilación. Las bibliotecas dinámicas afectarán los tiempos de enlace más que los tiempos de compilación, pero ¿vale la pena la complejidad adicional?
Sin embargo, de lo contrario, proporcionar bibliotecas dinámicas, especialmente para un producto bastante pequeño, no merece la pena.
Se podría escribir un libro completo sobre el tema de escribir bibliotecas dinámicas portátiles aplicables tanto a Windows como a Linux.
En Windows, la opción de usar __declspec (dllexport / dllimport) se puede aplicar a una clase completa. Sin embargo, es importante darse cuenta de que cualquier compilador que se use para generar la DLL solo se puede usar con objetivos creados con el mismo compilador o compiladores compatibles. Dentro del linaje de MS VC, muchas versiones NO son compatibles entre sí en este nivel, por lo que una DLL creada con una versión de Visual Studio puede no ser compatible con otras versiones, lo que genera una carga para el autor de generar DLL para cada compilador posible Versión a ser soportada.
Hay problemas similares con respecto a las bibliotecas estáticas. El código del cliente debe vincularse con la misma versión y configuración de CRT con la que se construye la DLL (¿está CRT vinculado estáticamente?). El código del cliente también debe elegir la misma configuración de manejo de excepciones y la configuración RTTI con la que se construye la biblioteca.
Cuando se debe considerar la portabilidad a Linux o UNIX (o Android / iOS), los problemas aumentan. El enlace dinámico es un concepto específico de la plataforma que no se maneja en C ++.
Las bibliotecas estáticas probablemente serían el mejor enfoque, y para esas __declspec (dllexport / dllimport) no se deben usar.
Con todo lo dicho en contra de las bibliotecas dinámicas, esta es una de las muchas formas de implementar eso en Windows (completamente inaplicable a Linux / UNIX / etc).
Un enfoque simple (quizás ingenuo pero conveniente) es descifrar a toda la clase tal como se exporta desde una DLL (importada en el código del cliente). Esto tiene una ligera ventaja sobre la declaración de cada función como exportada o importada, ya que este enfoque incluye datos de clase, y lo más importante es que el código de construcción / destructor / constructor AUTOMÁTICO que C ++ puede construir para su clase. Eso podría ser de vital importancia si no los atiende y exporta con cuidado.
En un encabezado que se incluirá para la producción de DLL:
#define DLL_EXPORT // or something similar, to indicate the DLL is being built
Eso se incluirá en la parte superior de su encabezado y declarará las clases de plantillas de su biblioteca. El encabezado que declara DLL_EXPORT solo se usa en un proyecto configurado para compilar la biblioteca DLL. Todo el código del cliente importará una versión de lo contrario vacía. (Existen innumerables otros métodos para hacer esto).
Como tal, DLL_EXPORT se define cuando se construye para la DLL, no se define cuando se crea el código del cliente.
En el encabezado de las declaraciones de clase de la plantilla de su biblioteca:
#ifdef _WIN32 // any Windows compliant compiler, might use _MSC_VER for VC specific code
#ifdef DLL_EXPORT
#define LIB_DECL __declspec(dllexport)
#else
#define LIB_DECL __declspec(dllimport)
#endif
O lo que prefiera ver en lugar de LIB_DECL como medio para declarar clases enteras exportables de DLL, importadas en el código del cliente.
Continuar con las declaraciones de clase como:
namespace N
{
template< typename T >
struct LIB_DECL Y
{
int x;
T v;
std::vector< T > VecOfT;
void f1( T & );
void f2( T &, int );
};
}
Declaraciones de instanciación explícitas para esto serían:
extern template struct LIB_DECL N::Y< int >;
extern template struct LIB_DECL N::Y< float >;
extern template struct LIB_DECL N::Y< double >;
extern template class LIB_DECL std::vector< int >;
extern template class LIB_DECL std::vector< float >;
extern template class LIB_DECL std::vector< double >;
Tome nota del std :: vector utilizado en la clase Y en este ejemplo. Considera el problema cuidadosamente. Si su biblioteca de DLL usa std :: vector (o cualquier clase de STL, esto es solo un ejemplo), la implementación que usó en el momento en que construye la DLL debe coincidir con lo que el usuario elige cuando construye el código del cliente. Las 3 instancias explícitas de vector coinciden con los requisitos de la clase de plantilla Y, y crean instancias de std :: vector dentro de la DLL, y con esta declaración se pueden exportar desde la DLL.
Considere, como el código DLL USE std :: vector. ¿Qué generaría el código en la DLL? Por experiencia, es obvio que la fuente de std :: vector está en línea, es un archivo de solo encabezado. Si su DLL crea una instancia del código del vector, ¿cómo accederá el código del cliente a este? El código del cliente "vería" la fuente std :: vector e intentaría su propia generación en línea de ese código donde el cliente accede a las funciones std :: vector. Si, y solo si, los dos coinciden exactamente, eso funcionaría. Cualquier diferencia entre la fuente utilizada para construir la DLL y la fuente utilizada para construir el cliente diferiría. Si el código del cliente tuviera acceso al std :: vector en la clase de plantilla T, sería un caos si el cliente usara una versión o implementación diferente (o tuviera configuraciones de compilador diferentes) cuando usara std :: vector.
Tiene la opción de generar explícitamente std :: vector e informar el código del cliente para usar ESE código generado declarando std :: vector como una clase de plantilla externa, para ser importado en el código del cliente (exportado en versiones DLL).
Ahora, en el CPP donde se construye la DLL, las definiciones de funciones de la biblioteca, debe crear instancias explícitas de las definiciones:
template struct LIB_DECL N::Y< int >;
template struct LIB_DECL N::Y< float >;
template struct LIB_DECL N::Y< double >;
template class LIB_DECL std::vector< int >;
template class LIB_DECL std::vector< float >;
template class LIB_DECL std::vector< double >;
En algunos ejemplos, como MS KB 168958, sugieren hacer una definición de la palabra clave externa, modificando este plan como:
#ifdef _WIN32 // any Windows compliant compiler, might use _MSC_VER for VC specific code
#ifdef DLL_EXPORT
#define LIB_DECL __declspec(dllexport)
#define EX_TEMPLATE
#else
#define LIB_DECL __declspec(dllimport)
#define EX_TEMPLATE extern
#endif
De modo que en el archivo de encabezado para DLL y compilaciones de cliente, simplemente puede indicar
EX_TEMPLATE template struct LIB_DECL N::Y< int >;
EX_TEMPLATE template struct LIB_DECL N::Y< float >;
EX_TEMPLATE template struct LIB_DECL N::Y< double >;
EX_TEMPLATE template class LIB_DECL std::vector< int >;
EX_TEMPLATE template class LIB_DECL std::vector< float >;
EX_TEMPLATE template class LIB_DECL std::vector< double >;
Si bien esto tiene la ventaja de emitir estas líneas una vez, en el encabezado, personalmente prefiero observar la palabra clave externa claramente en uso en el encabezado, por lo que sé que sin duda la única generación de código de lugar puede tener lugar en el CPP del Creación de DLL (donde aparecen, una segunda vez, sin la palabra clave externa). De esta manera, los extern en el encabezado son declaraciones hacia adelante, que no entran en conflicto con las definiciones de creación de instancias explícitas en el CPP, y evita ofuscar la palabra clave extern cuando se utiliza en el código del cliente. Es, quizás, una preferencia peculiar de mi propia.
Puede estar pensando, "¿qué pasa con el código de otro cliente y std :: vector". Bueno, es importante tener en cuenta. Su archivo de encabezado incluye std :: vector, pero recuerde que su DLL está construido con el código disponible para USTED en el momento de la compilación. Su cliente tendrá su propio encabezado, dentro de versiones similares de VC, que debería ser el mismo. El DEBER no es un buen plan, sin embargo. Podría ser diferente. Solo pueden asumir que VC 2015 es lo mismo y seguir adelante. Cualquier diferencia, ya sea el diseño del objeto, el código real ... cualquier cosa, podría condenar a la aplicación en ejecución con efectos muy extraños. Si exporta su versión, se recomienda a los clientes que incluyan las declaraciones de creación de instancias explícitas en todas sus unidades de compilación, de modo que todo use SU versión de std :: vector ... pero, hay un problema grave.
¿Qué pasaría si otra biblioteca también hiciera esto con una versión diferente de std :: vector?
Hace que el uso de STL sea un poco desagradable en estos contextos, por lo que hay una opción de diseño bastante buena que elimina esto. No exponga ningún uso de STL.
Si mantiene todo el uso de STL privado en su biblioteca y nunca expone un contenedor de STL al código del cliente, es probable que esté claro. Si elige eso en el diseño, no tiene necesidad de crear una instancia explícita del std :: vector (o cualquier STL) en su biblioteca.
Incluí el ejemplo para discutirlo, cómo está documentado por MS (KB 168958) y por qué es probable que no quiera hacerlo. Sin embargo, también surge el escenario inverso.
En la consulta original, se debe usar el uso de std :: string (o una de sus alternativas). Piense en esto: en la DLL, ¿de qué manera el uso de std :: string instanciar? ¿Qué pasa si hay alguna diferencia entre el código std :: string disponible cuando se construyó el DLL en comparación con el que utiliza el cliente por el que construyen? El cliente podría, después de todo, elegir usar algún otro STL que el proporcionado por MS. Por supuesto, podría estipular que no hagan eso, pero ... quizás podría crear una instancia explícita de std :: string como extern DENTRO de su DLL. De esa manera, no tiene un código STL creado dentro de la DLL, y ahora se informa al compilador que debe encontrar ese código creado por el cliente, no dentro de la DLL. Lo sugiero para la investigación y el pensamiento.
El problema insidioso que enfrenta es el siguiente: todo funcionará en su máquina, en sus pruebas, porque está usando un compilador. Sería impecable allí, pero podría fallar espectacularmente en las compilaciones de los clientes debido a las diferencias de código, o al establecer diferencias, lo suficientemente sutiles como para evitar las advertencias.
Entonces, supongamos que está de acuerdo y omita las últimas tres líneas en los ejemplos que ejemplifican std :: vector ... ¿está listo?
Eso depende de tu configuración de IDE, que te dejaré. La pregunta se centró en el uso de __declspec (dllxxxxx) y su uso, y hay varias maneras de implementar su uso, me centré en una. Independientemente de si debe cargar la biblioteca de forma explícita, basarse en las funciones de vinculación dinámica automática, considere DLL_PATH ... estos son temas generales de creación de DLL que conoce o están más allá del alcance real de la pregunta.
Probé esto en VS2013. Encontré sin el dllexport que enlaza, pero no se ejecuta. El uso de depends.exe muestra una importación sin resolver como se esperaba. Todo lo que se implemente en un archivo C ++ debe estar explícitamente diseñado para que un EXE lo vincule y las plantillas no son diferentes, por lo que no entiendo cómo lo ejecutará. Intente ingresar a él en el depurador y verifique que esté llamando al código en el archivo .cpp en la DLL.
Por cierto no necesitas dllimport. Para el cliente simplemente defina EXPORTDEF como nada.
Puedo malinterpretar tu pregunta (y nunca he usado Windows), por lo que quizás esta respuesta sea inapropiada.
Si todos los parámetros de plantilla permitidos para su clase de plantilla se conocen en el momento de la compilación de su biblioteca (en lugar del código de usuario de terceros), ¿por qué no incluir todas las definiciones e instancias en un archivo de origen (y esconderlo del archivo)? usuario)? Esta es la vieja técnica estándar de C ++ (no se necesitan cosas extrañas de C ++ 11).
// file.h:
template<typename T>
struct bar
{
static_assert(/* condition here */, "invalid template argument");
using return_type = /* definition here */;
static return_type from_string(std::string const&); // non-inline
};
// file.cc:
template<typename T>
typename bar<T>::return_type
bar<T>::from_string(std::string const&str)
{
/* some code here */
}
template struct bar<float>;
template struct bar<int>;
// etc., covering all T which pass the static_assert in struct bar<T>
editar (en respuesta al comentario de Ident)
Si desea basar from_string
en el operator>>(std::istream,T)
para todas las T
especiales, excepto unas pocas, entonces simplemente puede especializar la bar
plantillas. Aquí hay una implementación donde todos los casos, excepto los especiales, se tratan completamente en el archivo de encabezado.
// file.h:
template<typename T> // generic case: use istream >> T
struct bar
{
typedef T return_type;
static return_type from_string(std::string const&str)
{
return_type x;
std::istringstream(str) >> x;
return x;
}
};
template<>
struct bar<std::string> // string: return input
{
typedef std::string const&return_type;
static return_type from_string(std::string const&str)
{ return_type x; }
};
template<>
struct bar<special_type> // special_type: implement in file.cc
{
typedef special_type return_type;
static return_type from_string(std::string const&str); // non-inline
};
y file.cc
similar al anterior (aunque ahora implementa la especialización de plantillas en lugar de crear una única plantilla). También puede usar SFINAE para evocar comportamientos diferentes no para tipos individuales, sino para tipos que cumplan ciertas condiciones, etc.
Finalmente, es probable que desee definir una plantilla de función independiente from_string<T>
(y ocultar la bar<>
dentro de los namespace details
un namespace details
anidado)
template<typename T>
inline typename details::bar<T>::return_type
from_string(std::string const&str)
{ return details::bar<T>::from_string(str); }
Tenga en cuenta que podemos definir esto también sin referencia a la bar<T>::return_type
usando auto
:
template<typename T>
inline auto from_string(std::string const&str)
-> decltype(details::bar<T>::from_string(str))
{ return details::bar<T>::from_string(str); }