使用参数的 constexpr 运算符重载问题

constexpr operator overloading issues with using arguments

我正在制作一个简单的 class 继承自 std::array。关键是如果下标运算符用于越界索引,它应该抛出编译时错误。但是,我不断收到错误消息。这是简化后的代码。

#include <array>

using namespace std;

template<typename type, size_t size>
struct container : array<type,size>
{
    constexpr inline type& operator[](int index) const
    {
        static_assert(index<size,"");
        return ((static_cast<const array<type,size> >(*this))[index]);
    }

    template<class... bracelist>
    constexpr container(bracelist&&... B)
    :array<type,size>{std::forward<bracelist>(B)...}
    {}

    container() = default;
};

int main()
{
    constexpr container<int,4> myarray = {5,6,7,8};
    constexpr int number = myarray[2];
}

它给我的错误是:

main.cpp|80|error: non-constant condition for static assertion
main.cpp|80|error: 'index' is not a constant expression

但是,我在 return 语句中使用了 "index",并且注释掉 static_assert 使其工作正常。如果 index 不是一个常量表达式,我难道不能在 static_cast 之后的 std::array 的下标运算符中使用它吗?我不熟悉使用 constexpr 功能,因此我们将不胜感激。谢谢。

注意:我知道 std::array 的 constexpr 下标运算符已经这样做了,我只是想知道如何做以供将来使用。谢谢

您必须牢记的是,constexpr 函数可以在 运行 时使用非 constexpr 参数调用。 constexpr 表示函数在编译时计算的表达式(例如另一个 constexpr 或模板参数)中 可用 但不是唯一的。 constexpr 函数仍然可以以 经典 方式调用,即在 运行 时间使用 运行 时间变量。这意味着 constexpr 函数的参数不能也不是编译时常量。

它不适用于您的情况,但通常情况下,如果您知道一个参数将始终使用编译时常量调用,那么您可以将其设为模板参数。

constexpr void foo(int a)
{
    static_assert(a != 0); // illegal code because the parameter 
                           // cannot be a compile time constant
}

void test()
{
    int x;
    std::cin >> x;
    foo(x); // this is perfectly legal
}
template <int N>
void foo()
{
    static_assert(N != 0); // legal
}

void test()
{
    int x;
    std::cin >> x;
    foo<x>(); // illegal, x is not a compile time constant


    foo<24>(); // legal
    constexpr int c = 11;
    foo<c>();; // legal
}

这就是 std::get<N>(array) 的原因 — 这是确保以满足语言规则的方式传递 "compile-time value" 的唯一方法。您创建编译时 op[] 的尝试无法正常工作。您当然可以制作自己的模板化访问器,例如 std::get,但有人可能会问为什么不直接使用 std::array

constexpr 函数有 2 个非常有用的特性,它们之间的相互作用并不总是被充分理解。

  • 在 constexpr 上下文中,它们仅评估为 constexpr 参数采用的代码路径。

  • 在非 constexpr 上下文中,它们的行为与常规函数完全一样。

这意味着我们可以使用异常来达到很好的效果。

因为在 constexpr 上下文中,如果采用异常路径,这是一个编译器错误(在 constexpr 上下文中不允许抛出)。您会在编译器的错误输出中看到 "exception"。

示例:

#include <array>
#include <stdexcept>

template<typename type, std::size_t size>
struct container : std::array<type,size>
{
    constexpr auto operator[](std::size_t index) const
    -> type const&
    {
        if (index < size)
            return static_cast<const std::array<type,size>>(*this)[index];
        else
            throw std::out_of_range("index out of range" + std::to_string(index));
    }

    template<class... bracelist>
    constexpr container(bracelist&&... B)
    : std::array<type,size>{std::forward<bracelist>(B)...}
    {}

    container() = default;

};

int main()
{
    constexpr container<int,4> myarray = {5,6,7,8};
    constexpr int number = myarray[4];
}

示例输出:

main.cpp: In function 'int main()':
main.cpp:28:37:   in 'constexpr' expansion of 'myarray.container<int, 4>::operator[](4)'
main.cpp:13:81: error: expression '<throw-expression>' is not a constant expression
             throw std::out_of_range("index out of range" + std::to_string(index));

这种方法实际上比 static_assert 更通用,因为它在编译和运行时都有效。