How to initialize a constexpr std::array with templated constexpr member functions?

走远了吗. 提交于 2021-02-19 08:45:38

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!