在非 constexpr 函数中作为左值传递的变量上使用 `constexpr` 函数

Using a `constexpr` function on a variable passed as lvalue in a non-constexpr function

我正在使用 std::array 作为表示在编译时具有固定长度的向量的基础,并且想使用 std::array::size 作为 constexpr 函数来禁用交叉计算1D2D 向量的乘积。

当我在非 constexpr 函数中使用 std::array::size 时,它将我的向量作为左值参数,我得到一个错误:

main.cpp: In instantiation of ‘VectorType cross(const VectorType&, const VectorType&) [with VectorType = Vector<double, 3>]’:
main.cpp:97:16:   required from here
main.cpp:89:62: error: ‘vec1’ is not a constant expression
   89 |     return cross_dispatch<std::size(vec1), VectorType>::apply(vec1, vec2);
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
main.cpp:89:36: note: in template argument for type ‘long unsigned int’
   89 |     return cross_dispatch<std::size(vec1), VectorType>::apply(vec1, vec2);
      |            

这是使用 main 函数的最小工作示例:

#include <array>
#include <iostream>

using namespace std;

template<typename AT, auto D> 
class Vector final
: 
    public std::array<AT, D> 
{

public: 

    using container_type = std::array<AT,D>; 
    using container_type::container_type; 

    template<typename ... Args>
    constexpr Vector(Args&& ... args)
        : 
            container_type{std::forward<Args>(args)...}
    {}

    // Delete new operator to prevent undefined behavior for
    // std::array*= new Vector; delete array; std::array has 
    // no virtual destructors.
    template<typename ...Args>
    void* operator new (size_t, Args...) = delete;

};

using vector = Vector<double, 3>; 

template<std::size_t DIM, typename VectorType> 
struct cross_dispatch
{
    static VectorType apply(VectorType const& v1, VectorType const& v2)
    {
        static_assert(std::size(v1) < 3, "Cross product not implemented for 2D and 1D vectors."); 
        static_assert(std::size(v1) > 3, "Cross product not implemented for ND vectors."); 
        return VectorType();
    }
};

template<typename VectorType> 
struct cross_dispatch<3, VectorType>
{
    static VectorType apply(VectorType const& v1, VectorType const& v2)
    {
        return VectorType(v1[1]*v2[2] - v1[2]*v2[1], 
                          v1[2]*v2[0] - v1[0]*v2[2], 
                          v1[0]*v2[1] - v1[1]*v2[0]);

    }
};

template <typename VectorType> 
VectorType cross(VectorType const& vec1, VectorType const& vec2) 
{
    return cross_dispatch<std::size(vec1), VectorType>::apply(vec1, vec2);  
}

int main()
{
    vector p1 {1.,2.,3.}; 
    vector q1 {1.,2.,3.}; 

    cross(p1,q1);
}

我发现 提到了 GCC 8.0 中的错误,但我正在使用 g++ (GCC) 10.1.0

引用

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.8.1), would evaluate one of the following expressions:

... an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either it is initialized with a constant expression or its lifetime began within the evaluation of e

这是否意味着,在人类(非标准)语言中,在我的表达式中 e:=cross(p1,p2)p1p2 而不是 之前初始化为 constexpr 并且它们的生命周期 没有 e 开头,所以即使 p1p2 是一个对象其大小在编译时已知的数据类型 nad 其 mfunction size a constexpr mfunction,我现在必须在将它们绑定为左值之前将它们声明为 constexpr不是 constexpr?

的函数

下面,我回答为什么你的代码不起作用。专注于您的用例:正如其他人所说, std::array::size is not static, and all std::size 所做的就是调用该非静态函数。你最好的选择是简单地添加一个 static 大小函数到你的 Vector class:

static constexpr auto size() {
    return D;
}

您的 cross 实现将不起作用,因为您不能使用非常量表达式来初始化模板。请参阅 this SO answer 为什么函数参数不是常量表达式。

基本上,调用您的 cross 函数需要为 std::size(vec1) 的每个不同值生成一个 cross_dispatch 结构的新实例,这还需要知道 地址 在编译时每个给定的 vec1 因为 std::size 调用了一个非静态函数。从这里你应该可以看出,编译器根本不知道需要创建cross_dispatch的哪些实例。

上面,我提供了一个特定于您的用例的解决方案。如果您所做的不仅仅是测量 Vector 的大小,第二种解决方案是将对象作为模板参数传递(这将要求它们是 static):

template <typename VectorType, VectorType const& vec1, VectorType const& vec2>
constexpr VectorType cross()
{
    return cross_dispatch<std::size(vec1), VectorType>::apply(vec1, vec2);  
}

int main()
{
    static vector p1 {1.,2.,3.}; 
    static vector q1 {1.,2.,3.}; 

    cross<vector, p1, q1>();
}

因为p1q1是静态的,它们的地址可以在编译时知道,允许cross的模板被初始化。模板参数在 运行 时不会更改,因此 std::size(vec1) 现在是常量表达式。