Convertible templated math vector I've made a templated math vector struct with a templated type and templated dimension count.

I want my vectors to be convertible so I can easily make, for example, a vec&lt;int, 3&gt; from a vec&lt;float, 3&gt;

I want to do it in a safe way, or at least I'd like to know the ways it's unsafe.

I've never used static_cast or dynamic_cast and I've never used the operator user-defined conversion.

Here is what I came up with (only the relevant parts of the struct):

template&lt;class t, size_t dimensions&gt; struct vec{
    typedef t coord_type;       
    static constexpr size_t dimension_count = dimensions;

    std::array&lt;coord_type, dimension_count&gt; coords;

    template&lt;class new_coord_type&gt; operator vec&lt;new_coord_type, dimension_count&gt;() const{
        std::array&lt;new_coord_type, dimension_count&gt; casted_coords;
        std::transform(
            coords.begin(),
            coords.end(), 
            casted_coords.begin(),
            [](coord_type coord){return static_cast&lt;new_coord_type&gt;(coord);}
        );
        return {casted_coords};
    }
};


It seems to work how I want it to. For example, this compiles and gives me the correct results:

vec&lt;float, 3&gt; v1{1.1f, 2.5f, 3.9f};
vec&lt;int, 3&gt; v2 = v1; // converted correctly to {1, 2, 3}


The only way I could figure out how to cast the contents of an array is to use std::transform. I'm not sure if there's a better way.

Final questions: Is this unsafe or incorrect? How can this be improved?
I had to adjust your code style a bit before I could easily read the code. In particular:


Use InitialCaps for template parameters
Add a space before {
A newline after template&lt;...&gt; generally improves readability


Thus:

template&lt;class T, size_t Dimensions&gt;
struct vec {
    using coord_type = T;       
    static constexpr size_t dimension_count = Dimensions;

    std::array&lt;T, Dimensions&gt; coords;

    template&lt;class U&gt;
    operator vec&lt;U, Dimensions&gt;() const {
        std::array&lt;U, Dimensions&gt; casted_coords;
        std::transform(
            coords.begin(),
            coords.end(), 
            casted_coords.begin(),
            [](T coord) {
                return static_cast&lt;U&gt;(coord);
            }
        );
        return {casted_coords};
    }
};


Notice that I personally prefer to use the original template parameters T, Dimensions, etc. in places where your code used the member typedefs coord_type, dimension_count etc.; I find that this improves readability but I believe reasonable people may differ on the subject.

(In particular, the C++ Standard always uses member typedefs in declarations such as reference operator*() const as opposed to T&amp; operator*() const; but I think that's because the name reference is part of the API of the class, whereas the name T is given for exposition only. That's a Standard-ism that we don't necessarily need to emulate when writing our own code.)



Nit: Dimensions is technically the wrong name for a single dimension. Also, by capitalizing the template parameter, we've freed up the shorter name dimensions (or dimension) for the constexpr member variable. You never have to use a tediously long name like dimension_count if you manage your naming real estate effectively!



        return {casted_coords};


The extra braces here are a pessimization; they inhibit move-elision (a.k.a. Named Return Value Optimization). Remove them... oh wait, I see, casted_coords is a std::array, not a vec! Why are you constructing a separate array and then copying it into the vec? That seems pointless!

At the very least, this should be

return { std::move(casted_coords) };


but ideally you'd define casted_coords to be of type vec&lt;U, Dimensions&gt; and then transform the data straight into casted_coords.coords.begin().



            [](T coord) {
                return static_cast&lt;U&gt;(coord);
            }


This lambda takes its parameter by-copy, which is going to be expensive if T is, say, std::string. Prefer to take generic parameters by const&amp; or (in C++14) by perfect-forward:

            [](const T&amp; coord) {
                return static_cast&lt;U&gt;(coord);
            }

            [](auto&amp;&amp; coord) {
                return static_cast&lt;U&gt;(std::forward&lt;decltype(coord)&gt;(coord));
            }


In this case the former is better because it's less typing (and thus fewer chances to screw something up).



Your operator vec&lt;U, Dimensions&gt;() should be explicit, unless you have a darn good reason for it not to be. Most possible reasons I can think of do not qualify as "darn good."



In fact, I'd prefer to go further, and say that you shouldn't be using C++'s native "conversion" mechanisms for this kind of conversion at all. IMO you should write a user-defined function, along the lines of static_pointer_cast or any_cast, that does precisely and explicitly what you want.

template&lt;class To, class From, size_t Dim&gt;
auto static_vec_cast(const vec&lt;From, Dim&gt;&amp; from) -&gt; vec&lt;To, Dim&gt; {
    vec&lt;To, Dim&gt; to;
    std::transform(
        from.coords.begin(),
        from.coords.end(), 
        to.coords.begin(),
        [](const From&amp; x) {
            return static_cast&lt;To&gt;(x);
        }
    );
    return to;
}

int main() {
    vec&lt;int, 3&gt; a = {{1,2,3}};
    vec&lt;float, 3&gt; b = static_vec_cast&lt;float&gt;(a);
}


Season with SFINAE to taste.



However, this still has a major flaw: You're accidentally requiring that To be default-constructible! That's no good (especially if it does happen to be default-constructible but the default constructor is expensive). You can fix this issue, but it requires metaprogramming.

template&lt;class To, class From, size_t Dim, size_t... Is&gt;
auto static_vec_cast(const vec&lt;From, Dim&gt;&amp; from, std::index_sequence&lt;Is...&gt;) -&gt; vec&lt;To, Dim&gt; {
    vec&lt;To, Dim&gt; to = {{
        static_cast&lt;To&gt;(from.coords[Is])...
    }};
    return to;
}

template&lt;class To, class From, size_t Dim&gt;
auto static_vec_cast(const vec&lt;From, Dim&gt;&amp; from) -&gt; vec&lt;To, Dim&gt; {
    return static_vec_cast&lt;To&gt;(from, std::make_index_sequence&lt;Dim&gt;{});
}


The advantage of this approach is that it's hardly any more lines of code, but it's more correct, and it doesn't rely on running std::transform at runtime — it just generates the correct code directly inline.

Writing an efficient static_vec_cast&lt;U&gt;(vec&lt;T, Dim&gt;&amp;&amp;) is left as an exercise for the reader.