问题
This is a follow up of my question given here. In the end I want to create a constexpr std::array
containing text with an appended running index.
I wanted to try a different approach than in the previous question.
Nearly everything, what I do in the below code is constexpr. But maybe, it is simply the old problem of returning a pointer to a no longer existing variable. But, I doubt this.
Please see the following code, where the not working line in function main is marked.
#include <iostream>
#include <algorithm>
#include <iterator>
#include <array>
#include <string>
// Some example text
static constexpr const char BaseString[]{ "text" };
// To create something like "text123" as constexpr
template <const size_t numberToConvert, const char* Text>
class Converter {
public:
// Some helper variables
static constexpr size_t TextLength{ std::char_traits<char>::length(Text) };
static constexpr size_t NumberOfDigits{ ([]() constexpr noexcept {size_t result = 0; int temp = numberToConvert; for (; temp != 0; temp /= 10) ++result; return result; }()) };
static constexpr size_t ArrayLength{ (numberToConvert ? 1u : 2u) + NumberOfDigits + TextLength };
// Here we will build the text
char buf[ArrayLength]{};
// Constructor: Convert number to character digits
constexpr Converter() noexcept {
size_t i{ 0 }; for (; i < TextLength; ++i) buf[i] = Text[i]; // Copy text
if (numberToConvert == 0) buf[i] = '0';
else {
i = NumberOfDigits + TextLength - 1; // Convert number to character digits
int number = numberToConvert; for (; number; number /= 10)
buf[i--] = number % 10 + '0';
}
}
// cast operator
constexpr operator const char* () const noexcept { return buf; }
// For test purposes
constexpr const char* data() const noexcept { return buf; }
};
// Driver program
int main() {
// Temporaray constexprs
constexpr Converter<123, BaseString> conv123{}; // Default construction
constexpr auto conv2 = Converter<2, BaseString>(); // Assign / copy
// Build constexpr std::array and initialize it with constexprs
constexpr std::array< const char*, 2> convArray1{ conv123, conv2 };
// Show that it works
std::copy(convArray1.begin(), convArray1.end(), std::ostream_iterator<const char*>(std::cout, "\n"));
// Does compile, but not work. Array will be initialized with nullptr *******************************************
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };
std::cout << convArray2[0] << '\n' << convArray2[0] << '\n';
return 0;
}
So, I can create constexpr
"values" with my templated class. Those values can be used in the "initializer" list for a constexpr std::array
. But, if I want to use my class directly in the initializer list, then it compiles, but stores only nullptrs. Output of the program is:
text123
text2
╠╠╠╠╠╠╠╠╠╠╠╠╠╠<½7
╠╠╠╠╠╠╠╠╠╠╠╠╠╠<½7
Why does this happen? Or, is there a solution?
Compiled with Microsoft Visual Studio Community 2019, Version 16.8.2, C++17, Debug, X86
回答1:
Your code generating compile time dangling pointers (which should be impossible) on MSVC.
To fix:
template <const size_t numberToConvert, const char* Text>
class Converter {
// blah
std::array<char, ArrayLength> buf{};
constexpr operator std::array<char, ArrayLength>() const { return buf; }
constexpr std::array<char, ArrayLength> get() const { return *this; }
};
and remove other conversion operators and data
method.
template<const size_t numberToConvert, const char* Text>
constexpr auto Converted = Converter<numberToConvert, Text>{}.get();
and now use Converted<blah...>.data()
to get the pointers you want.
If you really want implicit conversion to character pointer:
template<const size_t numberToConvert, const char* Text>
struct Convertest {
constexpr operator char const*() const { return Converted<numberToConvert,Text>.data(); }
};
rename classes and variables however you like.
回答2:
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(),
Converter<2, BaseString>().data() };
Here, you are storing pointers to temporary variables - both Converter
objects seize to exist after ;
. Making dereferencing the pointers UB.
Clang rejects such code giving quite helpful message:
<source>:51:43: note: pointer to subobject of temporary is not a constant expression
<source>:51:55: note: temporary created here
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };
^
2 errors generated.
Execution build compiler returned: 1
I am not sure about the specific constexpr
rules but the code is unsafe even if it would compile.
回答3:
In Cpp-Reference you can see that
A constant expression is either [...] a prvalue core constant expression whose value satisfies the following constraints: [...] if the value is of pointer type, it holds - address of an object with static storage duration
So, for convArray1
constexpr std::array< const char*, 2> convArray1{ conv123, conv2 };
you have to make static
conv123
and conv2
// VVVVVV
static constexpr Converter<123, BaseString> conv123{};
static constexpr auto conv2 = Converter<2, BaseString>();
// ^^^^^^
because you can't have a constant expression from a pointer with not static storage.
For convArray2
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };
I don't see a way to get a constexpr
object from pointers inside temporary objects.
来源:https://stackoverflow.com/questions/65708899/how-to-initialize-a-constexpr-stdarray-with-templated-constexpr-member-functio