¿Es posible utilizar std::string en una expresión constante?
Usando C++ 11, Ubuntu 14.04, cadena de herramientas predeterminada de GCC .
Este código falla:
constexpr std::string constString = "constString";
error: el tipo 'const string {también conocido como const std::basic_string}' de la variable constexpr 'constString' no es literal... porque... 'std::basic_string' tiene un destructor no trivial
¿Es posible utilizarlo std::string
en un constexpr
? (aparentemente no...) Si es así, ¿cómo? ¿Existe una forma alternativa de utilizar una cadena de caracteres en un constexpr
?
A partir de C++20 , sí, pero solo si se std::string
destruye al final de la evaluación constante. Entonces, aunque su ejemplo aún no se compilará, algo como esto:
constexpr std::size_t n = std::string("hello, world").size();
Sin embargo, a partir de C++17 , puedes usar string_view
:
constexpr std::string_view sv = "hello, world";
A string_view
es un string
objeto similar a que actúa como una referencia inmutable y no propietaria de cualquier secuencia de char
objetos.
No, y su compilador ya le dio una explicación completa.
Pero podrías hacer esto:
constexpr char constString[] = "constString";
En tiempo de ejecución, esto se puede utilizar para construir un std::string
cuando sea necesario.
C++20 agregará constexpr
cadenas y vectores
Al parecer se ha aceptado la siguiente propuesta : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0980r0.pdf y agrega constructores como:
// 20.3.2.2, construct/copy/destroy
constexpr
basic_string() noexcept(noexcept(Allocator())) : basic_string(Allocator()) { }
constexpr
explicit basic_string(const Allocator& a) noexcept;
constexpr
basic_string(const basic_string& str);
constexpr
basic_string(basic_string&& str) noexcept;
además de las versiones constexpr de todos/la mayoría de los métodos.
No hay soporte a partir de GCC 9.1.0, lo siguiente no se puede compilar:
#include <string>
int main() {
constexpr std::string s("abc");
}
con:
g++-9 -std=c++2a main.cpp
con error:
error: the type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} of ‘constexpr’ variable ‘s’ is not literal
std::vector
discutido en: No se puede crear constexpr std::vector
Probado en Ubuntu 19.04.
Dado que el problema es el destructor no trivial, si el destructor se elimina del archivo std::string
, es posible definir una constexpr
instancia de ese tipo. Como esto
struct constexpr_str {
char const* str;
std::size_t size;
// can only construct from a char[] literal
template <std::size_t N>
constexpr constexpr_str(char const (&s)[N])
: str(s)
, size(N - 1) // not count the trailing nul
{}
};
int main()
{
constexpr constexpr_str s("constString");
// its .size is a constexpr
std::array<int, s.size> a;
return 0;
}
C++ 20 es un paso para hacer posible su uso std::string
en tiempo de compilación, pero P0980 no le permitirá escribir código como en su pregunta:
constexpr std::string constString = "constString";
la razón es que constexpr
std::string
solo se permite su uso en constexpr
funciones (contexto de evaluación de expresión constante). La memoria asignada por constexpr
std::string
debe liberarse antes de que dicha función regrese; esta es la llamada asignación transitoria, y esta memoria no puede "filtrarse" fuera del tiempo de ejecución a constexpr
objetos (almacenados en segmentos de datos) accesibles en tiempo de ejecución. Por ejemplo, la compilación de la línea de código anterior en la vista previa actual de VS2022 (versión cl: 19.30.30704) genera el siguiente error:
1> : error C2131: expression did not evaluate to a constant
1> : message : (sub-)object points to memory which was heap allocated during constant evaluation
esto se debe a que intenta realizar una asignación no transitoria que no está permitida; esto significaría una asignación en un segmento de datos del binario compilado.
En p0784r1, en el párrafo "Asignación no transitoria", puede encontrar que existe un plan para permitir la conversión de memoria transitoria en estática (el énfasis es mío):
¿Qué pasa con el almacenamiento que no se ha desasignado cuando se completa la evaluación? Podríamos simplemente no permitirlo, pero hay casos de uso realmente convincentes en los que esto podría ser deseable. Por ejemplo, esta podría ser la base para un tipo más flexible de clase "literal de cadena". Por lo tanto, proponemos que si una asignación constexpr no transitoria es válida (que se describirá a continuación), los objetos asignados se promueven a una duración de almacenamiento estático .
Hay una manera de exportar std::string
datos transitorios al exterior para que sean utilizables en tiempo de ejecución. Debe copiarlo en std::array
, el problema es calcular el tamaño final de std::array
, puede preestablecer un tamaño grande o calcular std::string
dos veces: una para obtener el tamaño y luego para obtener los datos reales. El siguiente código se compila y ejecuta con éxito en la versión preliminar 5 de VS2022 actual. Básicamente, une tres palabras con un delimitador entre palabras:
constexpr auto join_length(const std::vector<std::string>& vec, char delimiter) {
std::size_t length = std::accumulate(vec.begin(), vec.end(), 0,
[](std::size_t sum, const std::string& s) {
return sum + s.size();
});
return length + vec.size();
}
template<size_t N>
constexpr std::array<char, N+1> join_to_array(const std::vector<std::string>& vec, char delimiter) {
std::string result = std::accumulate(std::next(vec.begin()), vec.end(),
vec[0],
[&delimiter](const std::string& a, const std::string& b) {
return a + delimiter + b;
});
std::array<char, N+1> arr = {};
int i = 0;
for (auto c : result) {
arr[i++] = c;
}
return arr;
}
constexpr std::vector<std::string> getWords() {
return { "one", "two", "three" };
}
int main()
{
constexpr auto arr2 = join_to_array<join_length(getWords(), ';')>(getWords(), ';');
static_assert(std::string(&arr2[0]) == "one;two;three");
std::cout << &arr2[0] << "\n";
}