提问者:小点点

对作为左值在非constexpr函数中传递的变量使用“constexpr”函数


我使用std::array作为表示在编译时具有固定长度的向量的基础,并希望使用std::array::size作为Constexr函数来禁用1D2D向量的向量积计算。

当我在非constexpr函数中使用< code>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

引用答案

表达式 e 是核心常量表达式,除非按照抽象机器 (6.8.1) 的规则对 e 进行计算,否则将计算以下表达式之一:

…引用引用类型的变量或数据成员的id表达式,除非引用具有先前的初始化,并且它是用常量表达式初始化的,或者它的生命周期是从e的求值中开始的

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


共1个答案

匿名用户

下面,我回答为什么你的代码不起作用。专注于你的用例:正如其他人所说,std::array::size 不是静态的,而 std::size 所做的只是调用该非静态函数。最好的办法是简单地向 Vector 类添加一个静态大小函数:

static constexpr auto size() {
    return D;
}

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

基本上,调用cross函数需要为std::size(vec1)的每个不同值生成一个cross_dispatch结构的新实例,这也需要在编译时知道每个给定的vec1地址,因为td::size调用一个非静态函数。您应该能够从中看到,编译器根本不知道需要创建哪些<code>cross_discatch</code>实例。

上面,我提供了一个特定于您的用例的解决方案。如果您要做的不仅仅是测量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是静态的,所以它们的地址可以在编译时知道,从而允许初始化交叉的模板。模板参数在运行时不会改变,所以std::size(vec1)现在是一个常量表达式。