¿Es posible utilizar std::string en una expresión constante?

Resuelto GrumpyGramps asked hace 10 años • 5 respuestas

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::stringen un constexpr? (aparentemente no...) Si es así, ¿cómo? ¿Existe una forma alternativa de utilizar una cadena de caracteres en un constexpr?

GrumpyGramps avatar Nov 25 '14 16:11 GrumpyGramps
Aceptado

A partir de C++20 , sí, pero solo si se std::stringdestruye 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_viewes un stringobjeto similar a que actúa como una referencia inmutable y no propietaria de cualquier secuencia de charobjetos.

Joseph Thomson avatar Apr 11 '2017 02:04 Joseph Thomson

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::stringcuando sea necesario.

tenfour avatar Nov 25 '2014 10:11 tenfour

C++20 agregará constexprcadenas 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::vectordiscutido 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 constexprinstancia 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;
}
neuront avatar Jun 17 '2016 08:06 neuront

C++ 20 es un paso para hacer posible su uso std::stringen 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::stringsolo se permite su uso en constexprfunciones (contexto de evaluación de expresión constante). La memoria asignada por constexpr std::stringdebe 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 constexprobjetos (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::stringdatos 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::stringdos 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";
}
marcinj avatar Oct 15 '2021 21:10 marcinj