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>>()) {}
Related
This question already has an answer here:
Template tricks with const char* as a non-type parameter
(1 answer)
Closed 1 year ago.
I'm trying to provide a template argument with a const char* argument. My understanding is that you can provide a template argument with essentially an address, such as a function pointer and stuff like that. In this case I'd like to give it a pointer to an address holding a string, or char array, but it doesn't work:
constexpr const char* pString = "hello";
constexpr int* pToInt = nullptr;
// Am I right to have constexpr const char*? The constexpr says the pointer is const
// And the const before the char says the char is unmodifiable?
template <int* pToInt>
class TakeStaticIntPointer
{};
template <const char* pToString>
class TakeStaticStringPointer
{};
int main()
{
TakeStaticIntPointer<pToInt> myFoo; // Works fine
TakeStaticStringPointer<pString> myFoo2;
// Invalid non-type template argument of type const char*
}
I don't understand what's going on here. I'd also like to provide the template argument of a string literal "Hello". That's a compile-time constexpr, isn't it?
Unfortunately constexpr const char* Patting = "hello";, the address of a string literal is not a constexpr. Assignments of string literals and getting their addresses happen at link time, not at compile time. The solution with a real constexpr is like below.
constexpr const char pString[] = "hello";
constexpr int* pToInt = nullptr;
// Am I right to have constexpr const char*? The constexpr says the pointer is const
// And the const before the char says the char is unmodifiable?
template <int* pToInt>
class TakeStaticIntPointer
{};
template <const char* pToString>
class TakeStaticStringPointer
{};
int main()
{
TakeStaticIntPointer<pToInt> myFoo; // Works fine
TakeStaticStringPointer<pString> myFoo2; // Works fine with char[6]
}
In the following code, a class owns a dynamically-allocated array. It exposes a read-only access to this array via a public method that does an implicit conversion to const.
#include <array>
template <class T, std::size_t N>
class A {
const unsigned int size;
std::array<T, N> *s;
public:
A(const unsigned int _size, const std::array<T, N>& def) :
size(_size),
s(new std::array<T, N>[size])
{
for (unsigned int i = 0; i < size; i++)
s[i] = def;
}
~A() { delete[] s; }
std::array<T, N> const* const conf() const { return s; }
};
int main()
{
A a(10, std::array<int, 3>{0, 0, 0});
auto x = a.conf();
return 0;
}
Does the implicit const conversion in A::conf() give rise to an overhead (for example by invoking a copy-constructor of the elements of A::s)?
Does the implicit const conversion in A::conf() give rise to an overhead (for example by invoking a copy-constructor of the elements of A::s)?
No.
There is no "overhead", a pointer can be implicitly converted in its const-version. Generally, the conversion generates zero assembly instruction.
Additional Notes
Empirical Proof
A::conf() does not call any copy-constructor. From the generated assembly (gcc -O3):
A<int, 3ul>::conf() const:
movq %rdi, %rax
ret
As you can see, it just returns the pointer without producing any further instructions.
Note: I disabled the "inlining optimization" here, otherwise the entire function would be optimized producing zero overhead.
Small Tips
Maybe a little bit off-topic, but it is good practice to avoid managing raw memory in modern C++ (whenever possible).
What about using std::vector?
In the signature std::array<T, N> const* const conf() const the second const is always ignored by the compiler. You can omit making it more readable: std::array<T, N> const* conf() const.
conf() returns a pointer (std::array<T, N> const*). Thus auto x = a.conf(); deduces the type of x to be a const pointer std::array<T, N> const*. The address/pointer is copied by value (typically copy 4byte/8byte address depending on your system). No c'tor is called.
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.
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";
}
I am new to templates in C++.
Can anyone explain why my specialised constructor never gets executed.
It works when I remove the const and reference operator.
#include<iostream>
#include<string>
using namespace std;
template<typename T>
class CData
{
public:
CData(const T&);
CData(const char*&);
private:
T m_Data;
};
template<typename T>
CData<T>::CData(const T& Val)
{
cout << "Template" << endl;
m_Data = Val;
}
template<>
CData<char*>::CData(const char* &Str)
{
cout << "Char*" << endl;
m_Data = new char[strlen(Str) + 1];
strcpy(m_Data, Str);
}
void main()
{
CData<int> obj1(10);
CData<char*> obj2("Hello");
}
The output is
Template
Template
Because you cannot bind "Hello" to a const char*&.
The information dyp added in comments is quite interesting:
A string literal is an array lvalue, which can be converted to a pointer prvalue. A pointer prvalue cannot bind to a non-const lvalue reference like const char* &
Which means you can actually make it work by replacing const char*& by const char* const&, or even const char* && in c++11, not sure if this is really smart in your use case though.
UPDATE I got everything wrong, rewrote the answer completely.
First, this constructor
template<>
CData<char*>::CData(const char* &Str)
is not a specialization of CData(const T&), because the Str parameter here is a non-const reference to pointer to const char. So it's a definition of non-templated constructor CData(const char*&).
Second, "Hello" has type "array of n const char" (see What is the type of string literals in C and C++?) so it can't be converted to non-const reference. This is why "Template" constructor is called.
The correct specialization is
template<>
CData<char*>::CData(char* const& Str)
That is, it accepts a const reference to char*.
And after that you should remove CData(const char*&), unless you need for example this code to compile:
const char* foo = "foo";
CData<int> obj2(foo);
So here is the code:
template<typename T>
class CData
{
public:
CData(const T&);
private:
T m_Data;
};
template<typename T>
CData<T>::CData(const T& Val)
{
....
}
template<>
CData<char*>::CData(char* const& Str)
{
....
}
// warning: deprecated conversion from string constant to 'char*'
CData<char*> obj2("Hello"); // calls CData(char* const&)
The proper way of fixing above warning is to add another specialization:
template<>
CData<const char*>::CData(const char* const& Str)
{
...
}
CData<const char*> obj2("Hello"); // calls CData(const char* const&)