We will start with a bug, taken from real life. It spans across three files:
#include <string> struct Service { static const std::string NAME; };
#include "service.h" const std::string Service::NAME = "SERVICE1";
#include "service.h" #include <iostream> const std::string MSG = "service " + Service::NAME + " ready"; int main() { std::cout << MSG << std::endl; }
Question: what happens when this program is executed?
When I tested this program in my environment, even though I did not expect to get the intended output, I was still surprised at the result. The program runs without problems and outputs:
service ready
The program hits what is called static
initialization order fiasco: while initializing global B we need to read global A, but A may be initialized after B. Clearly, I have hit on the wrong initialization order, but I would have expected my program to crash or output some random characters. But apparently, in my version of the Standard Library, an std::string
filled with zeros (remember, all globals are statically zero-initialized) represents a valid, empty string. If my application crashes upon initialization, at least everyone gets feedback that something is wrong with it. But my application makes an impression that it works fine, and it displays something else than what I wanted.
This problem in reality manifested in a so characteristic way: when the developer tested the application in his environment, the application worked as intended. But when it was run in close-to-production environment it would crash upon start-up.
What can we do?
Once you have identified the source of the problem, it is easy to fix this occurrence of the bug. Instead of constant NAME
provide a function with a static automatic variable inside:
struct Service { static const std::string & NAME() { static const std::string _name = "SERVICE1"; return _name; } };
A static automatic variable is almost like a global: one instance is shared across all calls to the enclosing function, and is initialized only once: when the control reaches its declaration for the first time. This way, you guarantee that the string is initialized prior to being returned.
And now you will probably want to change global MSG
as well, to prevent future surprises:
const std::string& MSG() { static const std::string _msg = "service " + Service::NAME() + " ready"; return _msg; } int main() { std::cout << MSG << std::endl; }
But this solution is not ideal. First, as you probably noticed, in function main
I forgot to add the parentheses. But this is still valid C++: address of a function is implicitly convertible to bool
.
If you are following a good programming practice, and compile with warnings enabled (ideally treated as errors), compiler may help you detect the problem before you produce the executable. My GCC used with -Wall
tells me:
warning: the address of 'const string& MSG()' will always evaluate as 'true' [-Waddress]
One might argue that we can always inadvertently treat any function as an object, but here the situation is special: we have a constant wrapped into a function. Conceptually, we threat it as a constant. And this is in fact the second downside: we provided a function notation to read a constant value.
More objections arise. We now initialize our constant upon first use rather than on program startup. This means an unexpected slow-down (to allocate memory) in a place you didn’t want to have one. Remember that in practice the strings may be a bit longer than 6 chars and will not be SSO-ed. More, do you know what happens when the function is called for the first time concurrently from two threads? Synchronization (read: slow-down).
Plus, now the resources needed to store the global strings get released far after main
has finished. Profilers that detect memory leaks are often confused by this and report memory leak where there is none.
And finally, there is something unhealthy about managing resources before main
starts. C++ technically allows you to run program message loops outside main
. Two examples:
const bool _ = run_message_loop(), true; int main() {}
struct Launcher { Launcher() { std::set_terminate(&run_message_loop); } ~Launcher() { throw "launch"; } } _ {}; int main() {}
But doing this is evil. Managing resources before main
, although not that arrogant, is in the same spirit: it deprives main
of its role as the program’s entry point.
Towards the ideal solution
C++11 offers a tool for preventing static
initialization order fiasco: the ability to build compile-time constants:
constexpr double X = fun1(Y) + fun2(Z);
It doesn’t matter what fun1
, fun2
, Y
or Z
are. Unless the above initialization of X
triggers a compiler error, you are guaranteed that it is initialized statically, not using any run-time resources, and safe from any initialization order fiasco or undefined behavior. In other words, it is compile-time checked if you have any initialization issue.
But I cannot initialize a constexpr
std::string
like this: it is not a literal type. It has to allocate memory: it does not know how big the contained text will grow. But wait, this is true for a general string. In our case, however, we know exactly how many letters we want to store inside. In principle, until we require some run-time input from the environment, it should be possible to compute everything at compile-time and store it as compile-time constants.
To some extent this is what even C is doing. What happens here:
const char NAME[] = "SERVICE1"; static_assert(sizeof(NAME) == 8 + 1, "***");
This tells the compiler to prepare a static storage for the array. The size of the array is to be deduced from the string literal. The size of the array can be determined still at compile-time. This is almost what we need. We can even, to some extent, concatenate literals at compile time:
const char MSG[] = "service " "SERVICE1" " ready"; static_assert(sizeof(NAME) - 1 == 8 + 9 + 6, "***");
But unfortunately, we cannot do this:
const char NAME[] = "SERVICE1"; const char MSG[] = "service " NAME " ready";
If the language does not support this directly, we should be able to do it with a library: a library for concatenating strings at compile-time.
Implementing compile-time string concatenation
One additional constraint before we proceed. We are solving a real-life problem of real-life programs. This is year 2017, C++17 is almost there, C++14 has been there for more than two years; but in many environments (like mine) people still use C++11. I need the solution to be applicable to C++11.
The basic idea behind the solution can be illustrated by the signature of the following concatenation operator:
template <int N1, int N2> constexpr auto operator+(static_string<N1> s1, static_string<N2> s2) -> static_string<N1 + N2>;
Where static_string<N>
is like a built-in array with size N
, tracked statically as part of the type. We can use some meta-programming to compute the size of a concatenated string.
While the size of such string is as compile-time as it only can be, the values are “constexpr
-like”: they may be compile-time constants or not, depending on the usage. So, we are not as ambitious as Boost.Metaparse, which can generate different types for different character values at compile time:
// possible with Boost.Metaparse: auto b = PARSE_("bool"); // decltype(b) == bool auto c = PARSE_("char"); // decltype(c) == char
Our library will be more modest. We will focus on our goal: initialize a possibly concatenated string statically.
We will start with implementing a reference to a C-like string literal, which has the size embedded in the type, and which is a distinct type recognized by our concatenation library:
template <int N> class string_literal { const char (&_lit)[N + 1]; public: // ... };
C++ (like C) already has a designated storage for string literals. We will not be copying them; instead we will use a reference to this storage. Remember that the size of literal is always greater by one than the number of characters due to the trailing zero.
Now, we need a constructor:
constexpr string_literal(const char (&lit)[N + 1]) : _lit(lit) {}
With this we can write:
constexpr string_literal<4> NAME = "ABCD";
But the following unintended usage is also allowed:
constexpr char array [] = {'1', '2', '3', '4', '5'}; constexpr string_literal<4> NAME = array;
In order to prevent this, we enforce in the constructor that the last char
is a numeric value zero:
constexpr string_literal(const char (&lit)[N + 1]) : _lit((X_ASSERT(lit[N] == '\0'), lit)) {}
How to write macro X_ASSERT
that works like C-assert
in run-time and prevents compilation at compile-time, we have covered in the previous post.
Still, we have to explicitly state the size of the initialized string_literal
. Ideally, we would like the size thereof to be deduced from the initializer, as it is the case with C-arrays. But this is impossible in C++11.
But while deducing parts of the type is impossible in C++11, deducing the entire type works quite well if we add a function template into the mix:
constexpr auto NAME = literal("ABCD");
Not an ideal, because we declare a variable without a type, which sometimes leads to surprises. But the size of the literal can be now deduced if we implement literal
cleverly:
template <int N_PLUS_1> constexpr auto literal(const char (&lit)[N_PLUS_1]) -> string_literal<N_PLUS_1 - 1> { return string_literal<N_PLUS_1 - 1>(lit); }
This does almost the same trick as std::make_pair
, with one exception: because we want to match to literals of size N + 1, we cannot just type N + 1
directly in function’s parameter:
// WRONG: template <int N> constexpr auto literal(const char (&lit)[N + 1]) -> string_literal<N> { return string_literal<N>(lit); }
This is because of how the rules of template parameter matching work: you want an integer to be deduced, it has to be matched directly to a name, not to an arbitrary arithmetical expression.
One of the things we will need for our new type is to access the i
-th character of the string. This is quite easy to implement:
template <int N> class string_literal { const char (&_lit)[N + 1]; public: constexpr char operator[](int i) const { return X_ASSERT(i >= 0 && i < N), _lit[i]; } };
The additional const
is correct but redundant in C++11. However, in C++14 constexpr
on a function does not imply const
, as discussed in this post.
Now, we want to be able to concatenate two string_literal
s of different sizes. But what should be the result (type) of the concatenation? We cannot use the same type as now it is not a reference to an already existing literal. We will need a new type, which will store the array of characters:
template <int N> class array_string { char _array[N + 1]; // ... };
We will also store the trailing zero. This is in order to provide function c_str()
and the interface to C-like libraries that work with null-terminated character sequences. Writing the concatenation operator is easy: we just forward the call to the constructor of array_string
:
template <int N1, int N2> constexpr auto operator+(const string_literal<N1>& s1, const string_literal<N2>& s2) -> array_string<N1 + N2> { return array_string<N1 + N2>(s1, s2); }
The tough part is how to implement the constructor. In C++14, which has more relaxed constraints on constexpr
functions, it would be easy:
// in C++14: template <int N> class array_string { char _array[N + 1]; public: template <int N1, REQUIRES(N1 <= N)> constexpr array_string(const string_literal<N1>& s1, const string_literal<N - N1>& s2) { for (int i = 0; i < N1; ++i) _array[i] = s1[i]; for (int i = 0; i < N - N1; ++i) _array[N1 + i] = s2[i]; _array[N] = '\0'; } };
Do not be disturbed by this macro REQUIRES
, it is just a shorthand for an std::enable_if
:
# define REQUIRES(...) \ typename std::enable_if<(__VA_ARGS__), bool>::type = true
But such implementation will not work in C++11. In C++11 it is required that the body of the constexpr
constructor be empty: you can only initialize in the initializer list:
// in C++11: template <int N1, REQUIRES(N1 <= N)> constexpr array_string(const string_literal<N1>& s1, const string_literal<N - N1>& s2) : _array{ /* WHAT? */ } {}
Now, in order to achieve this almost impossible thing, you have to be familiar with parameter packs in variadic templates, and how they can be expanded, and with the following syntax:
template <typename ... Args> void f(Args &&... args) { g(std::forward<Args>(args)...); }
You will notice that std::forward<Args>(args)...
is a sort of pattern here. When function f
is called like f(1, 'c')
, the pattern is expanded to:
g(std::forward<int>(args1), std::forward<char>(args2));
Commas are added exactly where needed. But a sequence of int
s can also be a parameter pack:
template <int... Is> void f() { std::vector<int> v {Is...}; }
And when we call f<0, 1, 2>()
, the initialization of the vector gets expanded to:
std::vector<int> v {0, 1, 2};
This gets us close to the solution. But in our case we need two packs. If we somehow had them, we could initialize our array like this:
// Not C++ yet (no PACK1 or PACK2) template <int N1, REQUIRES(N1 <= N)> constexpr array_string(const string_literal<N1>& s1, const string_literal<N - N1>& s2) : _array{ s1[PACK1]..., s2[PACK2]..., '\0' } {}
In C++14, the Standard Library provides a tool exactly for this purpose: injecting int
-like parameter packs where you need them: integer_sequence
. It comes in two parts:
template <typename I, I... Is> class integer_sequence;
This part is used for “receiving” a pack (e.g., to expand a pattern). The second part is used for generating such integer_sequence
for a receiver:
template <typename I, I N> using make_integer_sequence = /*...*/;
This alias template generates instances of integer_sequence
according to the following expectation:
make_integer_sequence<int, 0> == integer_sequence<int>; make_integer_sequence<int, 1> == integer_sequence<int, 0>; make_integer_sequence<int, 2> == integer_sequence<int, 0, 1>; make_integer_sequence<int, 3> == integer_sequence<int, 0, 1, 2>;
That is, the N
in make_integer_sequence
determines the size of the sequence of integers in integer_sequence
, and this sequence is consecutive numbers starting from 0.
Now, I said that this is only available in C++14, and we are solving the problem for C++11. Luckily, it is quite easy to implement a similar tool oneself, and because we only need it to work with int
s (rather than any integral type), our tool can be smaller:
// the type used to receive the pack template <int... I> struct sequence {}; // auxiliary meta-function for making (N+1)-sized sequence // from an N-sized sequence template <typename T> struct append; template <int... I> struct append<sequence<I...>> { using type = sequence<I..., sizeof...(I)>; }; // recursive implementation of make_sequence template <int I> struct make_sequence_; template <int I> using make_sequence = typename make_sequence_<I>::type; template <> struct make_sequence_<0> // recursion end { using type = sequence<>; }; template <int I> struct make_sequence_ : append<make_sequence<I - 1>> { static_assert (I >= 0, "negative size"); };
Not the most efficient implementation, but it will do for illustration. If you do not understand what is going on here, don’t worry. The point is, such sequence can be implemented in C++11, and it exposes the desired properties:
make_sequence<0> == sequence<>; make_sequence<1> == sequence<0>; make_sequence<2> == sequence<0, 1>; make_sequence<3> == sequence<0, 1, 2>;
Now, in order to make use of it, we will have to provide two constructors for array_string
: one will be creating, and the other will be consuming sequence
s.
template <int N> class array_string { char _array[N + 1]; template <int N1, int... PACK1, int... PACK2> constexpr array_string(const string_literal<N1>& s1, const string_literal<N - N1>& s2, sequence<PACK1...>, sequence<PACK2...>) : _array{ s1[PACK1]..., s2[PACK2]..., '\0' } { } public: template <int N1, REQUIRES(N1 <= N)> constexpr array_string(const string_literal<N1>& s1, const string_literal<N - N1>& s2) // delegate to the other constructor : array_string{ s1, s2, make_sequence<N1>{}, make_sequence<N - N1>{} } { } };
We have a private constructor that does the actual initialization, and a public delegating constructor that creates the sequences. It can do it because sizes of the string_literal
s are encoded in their types.
Note the two additional function arguments in the private constructor. We do not even spell their names. We are not interested in their run-time values; we are not even interested in their types: we are only interested in being able to declare two parameter packs.
Now if we also implement operator[]
and function size
:
template <int N> class array_string { char _array[N + 1]; public: constexpr char operator[](int i) const { return X_ASSERT(i >= 0 && i < N), _array[i]; } constexpr std::size_t size() const { return N; } // ... };
We can already see that our tool does the basic task:
constexpr auto S1 = literal("ABCD"); constexpr auto S2 = literal("EFGH"); constexpr auto R = S1 + S2; static_assert(R.size() == 8, "***"); static_assert(R[0] == 'A', "***"); static_assert(R[7] == 'H', "***");
Of course, for such tool to be complete, we would have to add more member functions, like conversion to null-terminated string, possibly to std::string_view
(not C++11, but sometimes available as an experimental extension) or boost::string_view
, and add more overloads of the concatenation operator, working for different combinations of string_literal
, array_string
and const char *
. But there is no room for this in this blog post. Instead, you can try out a fully implemented library: static_string.
Some observations
One could argue that once I have type array_string
, I do not need type string_literal
anymore, because the former can handle whatever the latter can. It is true; but I find value in not having to copy the contents of the string, which the compiler has to store anyway. Minimum as it is, it is still an optimization. array_string
and string_literal
, once created have identical interfaces; they only differ by how they store the characters. Yet they are different types, and require of me to provide more and more overloads for concatenation operator with identical implementation. In a way, this is in the spirit of C++: performance trade-offs in the implementation are part of the contract. Nonetheless, to avoid the duplication, what I can do is to make array_string
and string_literal
two instantiations of the same template: template with specializations:
struct RefImpl {}; // tag class struct ArrayImpl {}; // tag class template <int N, typename Impl> class sstring // main template never used { static_assert(N != N, "***"); }; template <int N> class sstring<N, ArrayImpl> { char _array[N + 1]; // ... }; template <int N> class sstring<N, RefImpl> { const char (&_lit)[N + 1]; // ... }; template <int N> using array_string = sstring<N, ArrayImpl>; template <int N> using string_literal = sstring<N, RefImpl>;
This way I can provide one implementation of concatenation operator for all combinations of array_string
and string_literal
:
template <int N1, int N2, typename Tag1, typename Tag2> constexpr auto operator+(const sstring<N1, Tag1>& s1, const sstring<N2, Tag2>& s2) -> array_string<N1 + N2> { return array_string<N1 + N2>(s1, s2); }
Second, I mentioned that deducing the size of a string_literal
from initializer is impossible in C++11. The situation will be different in C++17. I can define a deduction guide:
// C++17 deduction guide: template <int N_PLUS_1> sstring(const char (&lit)[N_PLUS_1]) // <- match this -> sstring<N_PLUS_1 - 1, RefImpl>; // <- use these args
This says more less, “whenever class template sstring
is initialized and some or all of its template parameters have been omitted, and the arguments used for initialization match what I typed in parentheses, deduce the remaining template parameters as indicated.” It serves the purpose similar to function literal
, except that it will be used implicitly in contexts like this:
constexpr sstring S1 = "ABCD"; constexpr sstring S2 = "EFGH"; constexpr sstring R = S1 + S2;
This deduces not only the size, but also which implementation — array_string
or string_literal
— to use. Note that in the last line another deduction guide is used. This one is implicit, and it does not even have to be spelled out by programmers: when I initialize one instance of a class template (sstring
) with another instance of the same class template, deduce exactly the same template arguments.
Apart form concatenation, this library could also offer other operations, like taking a sub-string, which is easily implemented; but I never had a need for such things, so for now the library only implements what I know is needed.
Also, note that I am using type int
for representing string sizes, even though they never get negative. This is out of habit, which I consider good. When computing a size, through some (compile-time) arithmetical operation, it might happen that I will be subtracting a bigger number from a smaller one, and arrive at a negative result. If I use unsigned types, such results will be silently converted to a positive (and incorrect) value. I do not want that: I want negative sizes to be explicitly captured by static assertions, and reported verbosely by compiler.
Your point about int is well made. I am going to go change size_t to int in a personal library that wraps tuple operations, and add some more static_asserts. Thanks for the tip!
Nice read. I love especially when dev bloggers ask ‘what do you think this code does?.’
small typo in the first section “std::stirng”
Thanks! Fixed.
Compile time string concat should be easier with boost.hana
You mention boost::string_ref – apparently boost now has a string_view backport to replace boost::string_ref
> The support for boost::basic_string_ref and its specializations is deprecated; users are encouraged to switch to boost::basic_string_view. The support for boost::basic_string_ref will be removed in future releases.
http://www.boost.org/users/history/version_1_61_0.html
Other c++11/c++14 backports exist, e.g. https://github.com/tcbrindle/cpp17_headers.
Better than a backport, some compiler extensions support string_view
> libc++ supports string_view back to C++98 (as an extension), but the literals only exist in C++14 and later.
Looking ahead, starting to build on string_view from C++11 will be useful for future compatibility.
Thanks for pointing this out. Upon some investigation, it looks like while Boost does provide the implementation of string_view, it does not try to make it public/official. The quote about deprecation comes from Boost.Log library. string_view is otherwise not mentioned anyway.
Typos: `int mian() {}` and
“`
: _array{ N1[PACK1]…, N2[PACK2]…, ‘\0’ }
// should be:
: _array{ s1[PACK1]…, s2[PACK2]…, ‘\0’ }
“`
Thanks! Fixed.
Pingback: C++ Annotated: Apr – Aug 2017 | CLion Blog
Pingback: C++ Annotated: Apr – Aug 2017 - ReSharper C++ BlogReSharper C++ Blog
Yet another nail in the coffin of C++ …
Small comment:
While you are providing the code snippets, you gave the following two tags:
But while giving the example in the deduction guide, you used a different tag (the one from your implementation on github, which is slightly confusing)
RefImpl vs literal_ref
Thanks! Fixed.
I just arrived at your blog today and I was impressed… amazing work!
Immediately subscribed 😉
Just a minor pair of typos: the two `operator+` function templates end with an extra redundant `;` that could/should be removed.
Thanks!. Fixed.
How about using static object that holds all static variables?
class StaticManager
{
public:
const std::string Service::NAME = “SERVICE1”;
const std::string MSG = “service ” + Service::NAME + ” ready”;
};
static StaticManager Manager;
Technically, this would solve the initialization order problem, providing that this is the only problem one wants to solve in one’s project. This is equivalent to saying, “you will have no initialization order fiasco if you declare all your globals in a single file”. But that would break any attempt at separating the responsibilities of program into smaller components, and any encapsulation. Normally you want only one module or one class or one function to see a global. You do not want the global to be exposed to everyone.
The component that defines
NAME
doesn’t know that there will ever be a component with global variableMSG
. Maybe there will be none ever.”
…
run_before_main.cpp
const bool _ = run_message_loop(), true;
int mian() {}
…
”
maybe a typo “mian” instead of “main”
Thanks! Fixed.
minor fix to cool code to allow concatentation of any sstring (instead of only string_literal).
template
class sstring
{
template
constexpr sstring(const sstring & s1,
const sstring & s2,
sequence,
sequence )
: _array{ s1[PACK1]…, s2[PACK2]…, ‘\0’ }
{
}
template <int N1,typename ImplType1, typename implType2,
int… PACK1, int… PACK2,
typename std::enable_if<(N1 ::type = true>
constexpr sstring(const sstring & s1,
const sstring & s2 )
: sstring{ s1, s2, make_sequence{}, make_sequence{} }
{
}
char const * c_str() const { return &_array[0]; }
};
constexpr sstring S1 = “String1”;
constexpr sstring S2 = “String2”;
constexpr sstring R2 = S1 + S2;
constexpr sstring R3 = R2 + S1 + R2;
std::cout << R3.c_str() << std::endl;
$ String1String2String1String1String2
Artur, thanks for mentioning this. The blog post only has room to indicate certain ideas. In fact, the full library that it describes (https://github.com/akrzemi1/static_string) does implement the thing you describe. See the WandBox exammple: https://wandbox.org/permlink/tg70ZJNI9efx0g8r.
Oh i missed that, thx.
There is also coll thing you can implement in Your code i implemented for myself typename char_type, it makes this more generic.
constexpr sstring S2 = “String2″;
constexpr sstring S3 = L”String1″;
constexpr sstring S3a = u”String1″;
constexpr sstring S3b = U”String1”;
Am I wrong or I cannot directly use an your literal as argument for a std::string parameter where I used to put a const char *.
Having this function to call :
void MyClass::SetSectionConfig(const string&);
I cannot write this :
tankLeft.SetSectionConfig(CONFIG_Default+”_TANK”);
If I do, it gives the error :
no matching function for call to ‘CSimpleTank::SetSectionConfig(ak_toolkit::static_str::string)’
With :
static constexpr auto CONFIG_Default = sstr::literal(“CONFIG”);
My workaround (without modifying your code) is to write :
tankLeft.SetSectionConfig(std::string(CONFIG_Default+”_TANK”));
Is there anything preventing this ?
FWIW, I use gcc 4.9.2 with -std=c++14.
If you are referring to the GitHub implementation at https://github.com/akrzemi1/static_string, then you are right.
My goal is to provide an implicit conversion to string_view, but in order for the library to be portable to C++11, I need it to be conditional, controlled with a macro. So it has a block that allows you to select the best approximation of string_view:
Technically, you can abuse it to provide the conversion to
std::string
, but I am not sure I would recommend it. It follows the philosophy from string_view, that a change to string is resource-consuming, so it cannot be made implicit, and be performed accidentally. string_view is also not convertible to string: https://wandbox.org/permlink/fYKH5vkmvVP1fHNPThanks. Yes, I was refering to your github implementation.
I’m experimenting your conversion operator to std::string (AK_TOOLKIT_CONFIG_USING_STRING_VIEW defined to 4).
I don’t see why it shouldn’t be used this way, as it is as good (therefore as bad) as using implicit std::string conversion from a const char *.
My aim is to find a type useable with constexpr, allowing compile-time concatenation of literals, and usable as a replacement to most common of our current uses of const char *.
The ability to write :
tankLeft.SetSectionConfig(CONFIG_Default+”_TANK”);
that I’ve gained looks pretty promising to me.
I’m so unfamiliar with current C++ practise that my question may look like a troll, but it is not.
Sadly, it breaks code that compiles without the definition of this operator. It may also be considered as an example why implicit conversion is wrong. It first appears to me like a lack of operators (namely operator+()) consistent with the fact that implicit conversion is now possible.
void MyClass::SetSuffixForAllSections(const std::string& suffix)
{
SetSectionConfig(std::string(CONFIG_Default) + suffix); // compiled before change. Doesn’t anymore.
SetSectionConfig(CONFIG_Default + “someValue”); // compiles
SetSectionConfig(CONFIG_Default + suffix.c_str()); // doesn’t compile
SetSectionConfig(CONFIG_Default + suffix); // doesn’t compile, my new target
}
My analysis is probably wrong as :
std::string a = CONFIG_Default;
SetSectionConfig(a + suffix);
Compiles and my compiler tells me so (it’s always better to read error messages).
error: call of overloaded ‘basic_string(const ak_toolkit::static_str::string&)’ is ambiguous
/usr/include/c++/4.9/bits/basic_string.h:512:7: note: std::basic_string::basic_string(std::basic_string&&) [with _CharT = char; _Traits = std::char_traits; _Alloc = std::allocator]
basic_string(basic_string&& __str)
^
/usr/include/c++/4.9/bits/basic_string.h:495:7: note: std::basic_string::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits; _Alloc = std::allocator]
basic_string(const _CharT* __s, const _Alloc& __a = _Alloc());
^
/usr/include/c++/4.9/bits/basic_string.h:460:7: note: std::basic_string::basic_string(const std::basic_string&) [with _CharT = char; _Traits = std::char_traits; _Alloc = std::allocator]
basic_string(const basic_string& __str);
I have given it some thought. It looks to me that the use case the library was designed to handle is different than your use case.
The original use case was: “create a statically initialized string, concatenated form others. Then just display it.” Your use case is “… then, possibly use it for further run-time concatenation, or as a dynamically-allocated string.”
Maybe the library could be extended to address your use case, but I am not immediately sure that it is a good idea. I am very cautious about heap allocation, especially when it might be hidden. The philosophy I try now to follow (somewhat reflected in the library design) is that when you are performing a dynamic memory allocation (which is unpredictable in terms of consumed time), this should be made explicit in the code. For this reason the solution that you have to explicitly cast to
std::string
is acceptable for me. I admit that I might be too cautious about this, but I would not want to abandon this philosophy too easily.It seems that another workaround for you would be to define non-intrusive, non-member overloads for
operator+
. I realize this is like asking of you to implement thee missing parts of the library yourself, so I am not entirely happy about this advice.Thanks for thinking about this problem so carefully. It seems crazy that it’s so hard to make some truly compile time constant strings without heap/malloc. I have a use case similar use case to above person I think? Just static class convenience constants…
Doesn’t work, because cannot be “+”() d with std::string_view (or std::string).
I am sure you can understand my intended usage. Is there a way with your lib? Or another much simpler way?
#define AK_TOOLKIT_CONFIG_USING_STRING_VIEW 1
class A {
static constexpr auto VT100_ESC = sstr::literal(“\x1b”);
static constexpr auto VT100_CLS = VT100_ESC + “[2J”;
static constexpr auto VT100_BOX_DRAW_ON = VT100_ESC + “(0”;
static constexpr auto VT100_BOX_DRAW_OFF = VT100_ESC + “(B”;
static constexpr auto VT100_UNDERLINE_ON = VT100_ESC + “[4m”;
static constexpr auto VT100_UNDERLINE_OFF = VT100_ESC + “[0m”;
void method();
}
void A::method() {
std::string_view somestr = “hello”;
std::string_view vt = VT100_UNDERLINE_ON + somestr + VT100_UNDERLINE_OFF;
std::cout << vt;
}
compile fails with:
error: invalid operands to binary expression ('const ak_toolkit::static_str::string’ and ‘std::string_view’ (aka ‘basic_string_view’))
std::string_view vt = VT100_UNDERLINE_ON + somestr + VT100_UNDERLINE_OFF;
Your example would work if you were able to change the type of
somestr
:Here’s a full example: https://wandbox.org/permlink/ByaqmxyDE7gxUnUP.
If you cannot, it would be possible to add an overloaded
operator+
, however this operator would have to returnstd::string
as the presenceof
std::string_view
as an operand poisons the magic, and we have to go back to memory allocation.Do you plan to update this article using the C++20 constexpr std::string funcationality? Would love that!
No compiler has implemented this yet, to my knowledge.
Thanks for the reply. According to the Visual Studio 16.10 Preview 2 changelist, they have implemented constexpr std::string. So, hopefully it will be in the VS 16.10 stable release.
https://github.com/microsoft/STL/wiki/Changelog#vs-2019-1610-preview-2
Pingback: Assigning to const char * in constexpr constructor (c++17) fails using Android NDK - Tutorial Guruji
Pingback: [FIXED] Assigning to const char * in constexpr constructor (c++17) fails using Android NDK - FixeMe