1

I am using std::array as the base for representing vectors that have fixed length at compile time, and want to use std::array::size as a constexpr function to disable the calculation of cross product for 1D and 2D vectors.

When I use std::array::size in a non-constexpr function, that takes on my vectors as lvalue arguments, I get an error:

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

Here is the minimal working example with the main function:

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

I found this question that mentions a bug in GCC 8.0, but I am using g++ (GCC) 10.1.0.

To quote the answer

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

Does this mean, in human (non-standard) language, that in my expression e:=cross(p1,p2), p1 and p2 are not initialized before as constexpr and their lifetime did not begin with e, so even though p1 and p2 are objects of a data type whose size is known at compile time nad whose mfunction size is a constexpr mfunction, I now have to declare them as constexpr before binding them as lvalues in a function that's not constexpr?

7
  • 3
    This isn't especially minimal, and the question title talks about if constexpr but there's no if constexpr in this example? Commented Jun 23, 2020 at 13:30
  • 1
    Hmm, tl;dr. You can however add static constexpr size_t size() { return D; } to your Vector and do return cross_dispatch<VectorType::size(), VectorType>::apply(vec1, vec2); to get this particular problem solved. Commented Jun 23, 2020 at 13:44
  • I edited the title, there was another function template with if constexpr but I ended up using cross. Is Vector not inheriting also the static constexpr size_t size() from std::array, or did I forget another rule? Commented Jun 23, 2020 at 13:57
  • Is std::array::size() really static? I don't think it is - and even if it is, I don't think calling it via an instance (std::size(vec1)) works and that you have to call it like VectorType::size() Commented Jun 23, 2020 at 14:40
  • 2
    You will be interested by this blog post The constexpr array size problem, the author is the first commenter! @Barry Commented Jun 23, 2020 at 16:00

1 Answer 1

1

Below, I answer as to why your code doesn't work. Focusing on your use-case: as the others have said, std::array::size is not static, and all std::size does is call that non-static function. Your best bet is to simply add a static size function to your Vector class:

static constexpr auto size() {
    return D;
}

Your implementation of cross will not work because you cannot use non-constant expressions to initialize templates. See this SO answer on why function arguments are not constant expressions.

Basically, calling your cross function requires generating a new instance of the cross_dispatch structure for every different value of std::size(vec1), which also requires knowing the address of every given vec1 at compile-time since std::size calls a non-static function. You should be able to see from this that the compiler simply can't know which instances of cross_dispatch need to be created.

Above, I provided a solution specific to your use-case. If you were doing more than measuring the size of your Vector, a second solution would involve passing the objects as template parameters instead (which will require them to be 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>();
}

Because p1 and q1 are static, their addresses can be known at compile-time, allowing cross's template to be initialized. Template parameters do not change at run-time, so std::size(vec1) is now a constant expression.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.