Is it possible to use std::string in a constexpr? - c++

Using C++11, Ubuntu 14.04, GCC default toolchain.
This code fails:
constexpr std::string constString = "constString";
error: the type ‘const string {aka const std::basic_string}’ of
constexpr variable ‘constString’ is not literal... because...
‘std::basic_string’ has a non-trivial destructor
Is it possible to use std::string in aconstexpr? (apparently not...) If so, how? Is there an alternative way to use a character string in a constexpr?

As of C++20, yes, but only if the std::string is destroyed by the end of constant evaluation. So while your example will still not compile, something like this will:
constexpr std::size_t n = std::string("hello, world").size();
However, as of C++17, you can use string_view:
constexpr std::string_view sv = "hello, world";
A string_view is a string-like object that acts as an immutable, non-owning reference to any sequence of char objects.

No, and your compiler already gave you a comprehensive explanation.
But you could do this:
constexpr char constString[] = "constString";
At runtime, this can be used to construct a std::string when needed.

C++20 will add constexpr strings and vectors
The following proposal has been accepted apparently: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0980r0.pdf and it adds constructors such as:
// 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;
in addition to constexpr versions of all / most methods.
There is no support as of GCC 9.1.0, the following fails to compile:
#include <string>
int main() {
constexpr std::string s("abc");
}
with:
g++-9 -std=c++2a main.cpp
with error:
error: the type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} of ‘constexpr’ variable ‘s’ is not literal
std::vector discussed at: Cannot create constexpr std::vector
Tested in Ubuntu 19.04.

Since the problem is the non-trivial destructor so if the destructor is removed from the std::string, it's possible to define a constexpr instance of that type. Like this
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 is a step toward making it possible to use std::string at compile time, but P0980 will not allow you to write code like in your question:
constexpr std::string constString = "constString";
the reason is that constexpr std::string is allowed only to be used in constexpr function (constant expression evaluation context). Memory allocated by constexpr std::string must be freed before such function returns - this is the so called transient allocation, and this memory cannot 'leak' outside to runtime to constexpr objects (stored in data segments) accessible at runtime . For example compilation of above line of code in current VS2022 preview (cl version : 19.30.30704) results in following 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
this is because it tries to make a non-transient allocation which is not allowed - this would mean allocation into a data segment of the compiled binary.
In p0784r1, in "Non-transient allocation" paragraph, you can find that there is a plan to allow conversion of transient into static memory (emphasis mine):
What about storage that hasn't been deallocated by the time evaluation
completes? We could just disallow that, but there are really
compelling use cases where this might be desirable. E.g., this could
be the basis for a more flexible kind of "string literal" class. We
therefore propose that if a non-transient constexpr allocation is
valid (to be described next), the allocated objects are promoted to
static storage duration.
There is a way to export transient std::string data outside to make it usable at runtime. You must copy it to std::array, the problem is to compute the final size of std::array, you can either preset some large size or compute std::string twice - once to get size and then to get atual data. Following code successfully compiles and runs on current VS2022 preview 5. It basicly joins three words with a delimiter between words:
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";
}

Related

Why a std::array is not constant expression when it is the input of a templated function/generic lambda?

(Realted to this other question of mine; if you give a look at that too, I would really appreciate it.)
If std::array<T,N>::size is constexpr, then why does the following code not even compile?
#include <array>
#include <iostream>
constexpr auto print_size = [](auto const& array){
constexpr auto size = array.size();
std::cout << size << '\n';
};
int main() {
print_size(std::array<int,3>{{1,2,3}});
}
The error is the following:
$ g++ -std=c++17 deleteme.cpp && ./a.out
deleteme.cpp: In instantiation of ‘<lambda(const auto:1&)> [with auto:1 = std::array<int, 3>]’:
deleteme.cpp:10:42: required from here
deleteme.cpp:5:20: error: ‘array’ is not a constant expression
5 | constexpr auto size = array.size();
| ^~~~
But I wonder why.
At the lambda call site, the argument is known at compile time, and the lambda should be instantiated with auto equal to std::array<int,3>, where 3 is a compile time value, and so should be output of array.size().
What is wrong in my reasoning?
By the way, the same holds if I use a templated function instead of the generic lambda.
The problem is [expr.const]/5.12:
5 - An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
[...]
(5.12) an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
(5.12.1) it is usable in constant expressions or
(5.12.2) its lifetime began within the evaluation of E;
Since the variable array is a reference, it is not permitted to evaluate it (inside the expression array.size()), even though the evaluation doesn't actually do anything.
Passing array by value (const or non-const) makes the code valid:
constexpr auto print_size = [](auto const array){
constexpr auto size = array.size(); // ok
std::cout << size << '\n';
};
But taking a reference to that parameter and using it on the very next line is invalid:
constexpr auto print_size = [](auto const arr){
auto const& array = arr;
constexpr auto size = array.size(); // error
std::cout << size << '\n';
};
Note that gcc 9 incorrectly accepts this code; it is only since version 10 that gcc gets this correct.
gcc 10 still is noncompliant in a related area; it accepts calling a static constexpr member function on a reference. Using a constexpr static member of a reference as template argument This is incorrect and clang correctly rejects it:
struct S { static constexpr int g() { return 1; } };
void f(auto const& s) {
constexpr auto x = s.g(); // error
constexpr auto y = decltype(s)::g(); // ok
}
int main() { f(S{}); }
Addendum: this may change in future, per the paper P2280R1.
I was watching the 2014 Metaprogramming with Boost.Hana: Unifying Boost.Fusion and Boost.MPL presentation, where Louise Dionne touches this topic and explains what #super was telling me in the comments, but I was not understanding it.
This is my rewording of that concept: there's no such a thing as a constexpr function parameter, therefore whenever the lambda (actually its underlying operator()) is instantiated for a given type of array, that single instantiation is the one that should work both for constexpr and non-constexpr arguments of that type.
As Louis Dionne says in the linked presentation,
[…] you can't generate a constexpr inside a function if it depends on a parameter […] the return type of a function may only depend on the types of its arguments, not on their values […]
This give a way around the issue. Use array's type without using array's value:
constexpr auto print_size = [](auto const& array){
using array_type = decltype(array);
constexpr auto size = array_type{}.size();
std::cout << size << '\n';
};
which I think it's not different, in essence, from what #Jarod42 suggested in a comment:
You might use constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value
As an addition, I played around a bit more, because a last thing was bugging me: the size of std::array is not part of the value, but it's part of the type, so why can't I call size member function in contexprs? The reason is that std::array<T,N>::size() is sadly not static. If it was, one could call it as in the commented line below (the struct A is for comparison):
#include <array>
#include <iostream>
#include <type_traits>
template<std::size_t N>
struct A {
static constexpr std::size_t size() noexcept { return N; }
};
constexpr auto print_size = [](auto const& array){
constexpr auto size = std::decay_t<decltype(array)>::size();
std::cout << size << '\n';
};
int main() {
//print_size(std::array<int,3>{{1,2,3}});
print_size(A<3>{});
}

store a string in a constexpr struct

Is it possible to store a string in a constexpr struct:
So far I could only come up with:
struct A
{
constexpr A(std::string_view n): m_name(n) {}
constexpr auto name(){ return m_name; }
std::string_view m_name; // This might become dangling!!
}
which is cleary only a good idea if this class is only used like this
A a = {"Hello"};
constexpr A b = {"World"};
and not like this
auto makeA(std::string n) { return A{n}; }
A a = makeA("Hello"); // Dangling internal std::string_view
I need the constexpr to construct the struct at compile time.
Is it possible to make this safer at run-time, because with std::string_view, its not.
This is not really a question of safety as much as it is a question of semantics. Nothing prevents you from doing the exact same thing at compiletime:
constexpr A blub()
{
char str[] = "asdf";
return { str };
}
Since there is no way to ever call this function in a core constant expression, a program that contains code like this is ill-formed, no diagnostic required [dcl.constexpr]/5, which really isn't any better than invoking undefined behavior at runtime…
Compiletime or not, you have to ask yourself the question: Should this struct own a string or refer to an existing string? I would strongly recommend against making your struct own a string in a runtime context and refer to an existing string in a compiletime context, even if you find a way to pull this off in theory. We're talking about completely different semantics here. Completely different semantics should generally better be modeled by different types rather than one type that completeley changes its meaning depending on context…
If you want to make a constexpr struct own a string, you'll currently have to resort to some constexpr string implementation such as, e.g., this one. Since your question is tagged with c++20, note that std::string will be useable in a constexpr context starting with C++20 [basic.string]. So, in C++20, you will be able to just have the member be an std::string…
You might do:
template<typename Char, Char... Cs>
struct CharSeq
{
static constexpr const Char s[] = {Cs..., 0}; // The unique address
};
// That template uses the extension
template<typename Char, Char... Cs>
constexpr CharSeq<Char, Cs...> operator"" _cs() {
return {};
}
See my answer from String-interning at compiletime for profiling to have MAKE_STRING macro if you cannot used the extension (Really more verbose, and hard coded limit for accepted string length).
Then
struct A
{
template <char ... Cs>
constexpr A(CharSeq<char, Cs...>) : m_name(CharSeq<char, Cs...>::s) {}
constexpr auto name(){ return m_name; }
std::string_view m_name;
};
With only valid usages similar to:
A a = {"Hello"_cs};
constexpr A b = {"World"_cs};

Check length of string literal at compile time

I would like to check length of my string literals at compile time. For now I am thinking about the following construction, but can't complete it:
#define STR(s) (sizeof(s) < 10 ? s : /* somehow perform static_assert */)
void foo(const char* s) {}
int main() {
foo(STR("abc")); // foo("abc")
foo(STR("abcabcabcabc")); // compile time error: "String exceeds 10 bytes!"
}
This is C++, where there are superior options to macros. A template can give you the exact semantics your want.
template<std::size_t N>
constexpr auto& STR(char const (&s)[N]) {
static_assert(N < 10, "String exceeds 10 bytes!");
// < 11 if you meant 10 characters. There is a trailing `\0`
// in every literal, even if we don't explicitly specify it
return s;
}
The array reference argument will bind to string literals, not pointers (that can trip your macro), deduce their size, and perform the check in the body of the function. Then it will return the reference unchanged if everything checks out, allowing even for continued overload resolution.
I will add to #StoryTeller - Unslander Monica great answer,
If you need (like me) to pass and argument for string's max length, you can expand the implementation to be more generic :
template<const int MAX_LEN, std::size_t N>
constexpr auto& STR(char const (&s)[N])
{
static_assert(N < MAX_LEN, "String overflow!");
return s;
}
And if you need several known length's, you can use template specialization :
template<std::size_t N>
constexpr auto & STR16(char const (&s)[N])
{
return STR<16>(s);
}
The first function can be a generic version, where's the second can have access to project's consts.

Compile-time strings: constructor overload precedence between `const char *` / `const char[]`

I am trying to make a compile-time string class. I took a few hints from this post. Unfortunately, I'm stuck on constructor overload precedence: the const char[] constructor is being ignored in favor of the const char* constructor. Any tips would be appreciated!
class string {
public:
// Can be done compile time. Works lovely! except...
template<size_t N>
constexpr string(const char(&char_array)[N])
: ptr_(char_array), length_(N-1) {}
// This override gets called instead. I *must* keep this constructor.
string(const char* const str)
: ptr_(str) {
length_ = strlen(str);
}
// Ugly hack. (not acceptable)
template<size_t N>
constexpr string(const char(&char_array)[N], double unused)
: ptr_(char_array), length_(N-1) {}
private:
const char* ptr_;
int length_;
};
constexpr const char kConstant[] = "FooBarBaz";
constexpr string kString(kConstant); // Error: constexpr variable 'kString' must be initialized by a constant expression (tries to call wrong overload)
constexpr string kString(kConstant, 1.0f); // ugly hack works.
There's lots of cool things I can do if I can make compile-time string constants.
string equality testing is faster on string than const char *
Eliminate run-time overhead of implicit conversions from const char * to string that call strlen() on compile-time constant strings.
Compile-time string sets that do equality testing instead of hashing for size < N. (this is multiple cpus of overhead on one application I'm looking at)
This is a bit ugly, but it should work:
template<class T, class = std::enable_if_t<std::is_same_v<T, char>>>
string(const T * const & str)
: ptr_(str) {
length_ = strlen(str);
}
The trick is that taking the pointer by const reference blocks array-to-pointer decay during template argument deduction, so when you pass an array, the compiler can't deduce T and the constructor is ignored.
The drawback is that this would also reject other things that are implicitly convertible to const char *.
An alternative might be to accept everything convertible to const char *, and then dispatch based on whether said thing is an array.
template<size_t N>
constexpr string(const char(&char_array)[N], std::true_type)
: ptr_(char_array), length_(N-1) {}
string(const char * str, std::false_type)
: ptr_(str) {
length_ = strlen(str);
}
template<class T, class = std::enable_if_t<std::is_convertible_v<T, const char *>>>
constexpr string(T&& t)
: string(std::forward<T>(t), std::is_array<std::remove_reference_t<T>>()) {}

Can I mix compile time string comparison with MPL templates?

I got this compile time string comparison from another thread using constexpr and C++11 (http://stackoverflow.com/questions/5721813/compile-time-assert-for-string-equality). It works with constant strings like "OK"
constexpr bool isequal(char const *one, char const *two) {
return (*one && *two) ? (*one == *two && isequal(one + 1, two + 1))
: (!*one && !*two);
}
I am trying to use it in the following context:
static_assert(isequal(boost::mpl::c_str<boost::mpl::string<'ak'>>::value, "ak"), "should not fail");
But it gives me an compilation error of static_assert expression is not an constant integral expression.
Can I do this?
The problem is that the value member of mpl::c_str is not marked as constexpr. Until the library authors decide to include support for constexpr, you are pretty much screwed, unless you are willing to modify your Boost code (or create your own version of c_str). If you decide to do so, the modification is quite simple: you just need to locate BOOST_ROOT/boost/mpl/string.hpp and replace this
template<typename Sequence>
struct c_str
{
...
static typename Sequence::value_type const value[BOOST_MPL_LIMIT_STRING_SIZE+1]
};
template<typename Sequence>
typename Sequence::value_type const c_str<Sequence>::value[BOOST_MPL_LIMIT_STRING_SIZE+1] =
{
#define M0(z, n, data) \
mpl::aux_::deref_unless<BOOST_PP_CAT(i, n), iend>::type::value,
BOOST_PP_REPEAT(BOOST_MPL_LIMIT_STRING_SIZE, M0, ~)
#undef M0
'\0'
};
by this
template<typename Sequence>
struct c_str
{
...
static constexpr typename Sequence::value_type value[BOOST_MPL_LIMIT_STRING_SIZE+1] =
{
#define M0(z, n, data) \
mpl::aux_::deref_unless<BOOST_PP_CAT(i, n), iend>::type::value,
BOOST_PP_REPEAT(BOOST_MPL_LIMIT_STRING_SIZE, M0, ~)
#undef M0
'\0'
};
};
// definition still needed
template<typename Sequence>
constexpr typename Sequence::value_type c_str<Sequence>::value[BOOST_MPL_LIMIT_STRING_SIZE+1];
Hmm, after digging a bit more, it turns out the problem is more complex than I thought. In fact, static constants can be used in constexpr; the true problem is that c_str<T>::value is an array, and your function takes pointers as parameters. As a consequence, the compiler needs to decay the array, which boils down to taking the address of its first element. Since addresses are a runtime concept, it is not possible to take the address of an object in a constexpr.
To solve the issue, I tried to write a second version of isequal that operates on arrays rather than on pointers:
template <int N, int M>
constexpr bool isequal(char const (&one)[N], char const (&two)[M], int index)
{
return (one[index] && two[index]) ?
(one[index] == two[index] && isequal(one, two, index + 1)) :
(!one[index] && !two[index]);
}
template <int N, int M>
constexpr bool isequal(char const (&one)[N], char const (&two)[M])
{
// note: we can't check whether N == M since the size of the array
// can be greater than the actual size of the null-terminated string
return isequal(one, two, 0);
}
constexpr char hello[] = "hello";
static_assert(isequal(hello, hello), "hello == hello");
constexpr char zello[] = "zello";
static_assert(!isequal(hello, zello), "hello != zello");
constexpr char hel[] = "hel";
static_assert(!isequal(hello, hel), "hello != hel");
Unfortunately, this code does not work with mpl::c_str; in fact, the problem is that static const arrays are not compile-time values, unlike integral constants. So we're back the beginning: unless value is marked as constexpr, there is no way to use it in a constant expression.
As to why the code I gave initially fails, I can't answer right now as my version of gcc (4.6) fails to compile it altogether...
After updating gcc, it turns out value needs to be defined outside the class, even though it is declared and initialized in the class (see this question). I edited the code above with the correction.