【问题标题】:How to initialize a constexpr std::array with templated constexpr member functions?如何使用模板化的 constexpr 成员函数初始化 constexpr std::array?
【发布时间】:2021-04-18 21:03:13
【问题描述】:

这是我的问题given here 的跟进。最后,我想创建一个constexpr std::array,其中包含带有附加运行索引的文本。

我想尝试与上一个问题不同的方法。

几乎所有内容,我在下面的代码中所做的是 constexpr。但也许,这只是返回指向不再存在的变量的指针的老问题。但是,我对此表示怀疑。

请看下面的代码,其中 main 函数中的 not working 行被标记了。

#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;
}

所以,我可以用我的模板类创建constexpr“值”。这些值可以在constexpr std::array 的“初始化程序”列表中使用。但是,如果我想直接在初始化列表中使用我的类,那么它会编译,但只存储 nullptrs。程序的输出是:

text123
text2
╠╠╠╠╠╠╠╠╠╠╠╠╠╠<½7
╠╠╠╠╠╠╠╠╠╠╠╠╠╠<½7

为什么会这样?或者,有什么解决办法吗?


使用 Microsoft Visual Studio Community 2019 版本 16.8.2、C++17、Debug、X86 编译

【问题讨论】:

  • 它不能使用 Clang 或 GCC 编译,但可以使用 MSVC godbolt.org/z/737Eb4
  • 那么显然是一些 MSVC 扩展使其可编译。真是悲剧。
  • Array will be initialized with nullptr 你为什么这么认为?它看起来像垃圾指针,而不是零
  • 我在某处看到它使用调试器并进入库代码(在 xutil 和 xstring 中)。但这可能与其他一些代码变体有关。但它当然可以指向任何地方。无论如何,它不起作用。 . .

标签: c++ arrays templates c++17 constexpr


【解决方案1】:
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), 
                                                  Converter<2, BaseString>().data() };

在这里,您正在存储指向临时变量的指针 - 两个 Converter 对象都在 ; 之后存在。取消引用指针 UB。

Clang 拒绝这样的代码,给出非常有用的信息:

<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

我不确定具体的constexpr 规则,但代码即使编译也是不安全的。

【讨论】:

  • 是的,UB,我是这样假设的,因此在我的问题中提到了它。 MSVC 以某种方式对其进行了编译,并给我的印象是它通过使用 constexpr 以某种方式工作。 . .
【解决方案2】:

Cpp-Reference你可以看到

常量表达式是 [...] prvalue 核心常量表达式,其值满足以下约束:[...] 如果值是指针类型,则它保存 - 具有静态存储持续时间的对象的地址

所以,对于convArray1

constexpr std::array< const char*, 2> convArray1{ conv123, conv2 };

你必须使staticconv123conv2

// VVVVVV
   static constexpr Converter<123, BaseString> conv123{};
   static constexpr auto conv2 = Converter<2, BaseString>();
// ^^^^^^

因为您不能从非静态存储的指针中获得常量表达式。

对于convArray2

constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };

我看不到从临时对象中的指针获取constexpr 对象的方法。

【讨论】:

  • 是的,我同意。我的方法行不通。所以,我会继续寻找下一个方法。 . .
【解决方案3】:

您的代码在 MSVC 上生成编译时悬空指针(这应该是不可能的)。

修复:

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; }
};

并删除其他转换运算符和data 方法。

template<const size_t numberToConvert, const char* Text>
constexpr auto Converted = Converter<numberToConvert, Text>{}.get();

现在使用Converted&lt;blah...&gt;.data() 获取您想要的指针。

如果你真的想隐式转换为字符指针:

template<const size_t numberToConvert, const char* Text>
struct Convertest {
  constexpr operator char const*() const { return Converted<numberToConvert,Text>.data(); }
};

随意重命名类和变量。

【讨论】:

  • 修复上述示例中的错误后,它将在 MSVC、Clang 和 gcc 上编译。它有效。很难理解为什么,我认为关键是模板变量的使用。没有机会忽略这一点。但有了它,它就起作用了。我会在这里给自己一个额外的答案:stackoverflow.com/questions/65664672/…
  • @ArminMontigny 是的,// blah 可能行不通。 ;) 我还搞砸了什么?
  • std::array 中的模板参数序列以及“get”函数声明中的附加括号。不过真的没问题。正如所说。它完美地工作。但我还是不明白,为什么附加的模板变量是强制性的,不能省略。反正。很好的答案。 +1+接受
  • @ArminMontigny 模板化变量为数组提供(编译时和运行时)存储,因此可以形成指向它的指针。指针必须指向事物。你可以用其他方式来做,但我认为一个简单的模板 constexpr 变量是最简单的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-07
  • 2012-07-16
  • 2020-09-20
  • 1970-01-01
  • 2018-07-21
相关资源
最近更新 更多