Home > Back-end >  Struct template and constructor with initializer list
Struct template and constructor with initializer list

Time:05-21

I'm trying to better understand templates and for this purpose I made an educational struct:

template<typename T, size_t N>
struct SVectorN
{
    SVectorN(const T(&another)[N]);
private:
    T components[N];
};

Constructor that I made allows me to make an instance, like this:

double input[4] = { 0.1, 0.2, 0.3, 0.4 };
SVectorN<double, 4> t(input);

But I can't figure out how to support initializating like this:

SVectorN<double, 4> c = { 1.3, 1.4, 1.5, 1.6 };

Or even better something like this:

SVectorN c(1.3, 1.4, 1.5, 1.6 ); // SVectorN<double, 4>

Is it possible or am I missing something comepletely?

Thanks

CodePudding user response:

Both approaches are possible, initialization in C is tricky simply because of how many ways there are and their subtle differences.

I would recommend something like this:

#include <cstdint>
#include <utility>

template <typename T, std::size_t N> struct SVectorN {
    SVectorN(const T (&another)[N]);

    template <typename First, typename Second, typename... Tail>
    SVectorN(First &&f, Second &&s, Tail &&...t)
        : components{std::forward<First>(f), std::forward<Second>(s),
                     std::forward<Tail>(t)...} {}

  private:
    T components[N];
};

int main() {
    double input[4] = {0.1, 0.2, 0.3, 0.4};
    SVectorN<double, 4> t(input);

    SVectorN<double, 4> c = {1.3, 1.4, 1.5, 1.6};
    SVectorN<double, 4> d{1.3, 1.4, 1.5, 1.6};
    SVectorN<double, 4> e(1.3, 1.4, 1.5, 1.6);
}

I had to explicitly enforce presence of at least two arguments in the variadic ctor, otherwise it would be a better overload match for t's construction and that would fail.

c,d,e are practically the same initialization if there is not ctor with std::initializer_list. Which there is not because initializing an array with one cannot be easily done.

Now, if you want to automatically deduce the template parameters, that is possible thanks to CTAD. To simplify that link, you have to just specify to the compiler how to deduce class's template arguments from a constructor's arguments.

For example the following:

template <class T, std::size_t N>
explicit SVectorN(const T (&)[N]) -> SVectorN<T, N>;
template <typename F, typename S, typename... Tail>
SVectorN(F &&, S &&, Tail &&...) -> SVectorN<F, sizeof...(Tail)   2>;

will allow:

    SVectorN tx(input);
    SVectorN cx = {1.3, 1.4, 1.5, 1.6};
    SVectorN dx{1.3, 1.4, 1.5, 1.6};
    SVectorN ex(1.3, 1.4, 1.5, 1.6);

CodePudding user response:

I really like the answer from @Quimby, but I'm not sure if it would work with one argument. Alternatively, you could overload the constructors with std::initializer_list and variadic. It's not very efficient at this stage, but you can optimize it as per your needs.

#include <iostream>

template <typename T, size_t N>
struct SVectorN
{
    private:
        T components[N];
    
    public:
        SVectorN(const T(&another)[N])
        {
            std::copy(std::begin(another), std::end(another), components);
        }
    
        SVectorN(std::initializer_list<T> &&init_)
        {
            std::copy(std::begin(init_), std::end(init_), components);
        }
    
        template <typename ... U>
        SVectorN(U ...val_)
        {
            T tmp[] = { val_ ... };
            std::copy(std::begin(tmp), std::end(tmp), components);
        }
    
        void print() const
        {
            for (auto &&c : components)
                std::cout<< c <<",";
            std::cout<<std::endl;
        }
};

int main()
{
    double input[4] = { 0.1, 0.2, 0.3, 0.4 };
    
    SVectorN<double, 4> t(input);
    t.print();
    
    SVectorN<double, 4> c = { 1.3, 1.4, 1.5, 1.6 };
    c.print();
    
    SVectorN<double, 4> d(1.3, 1.4, 1.5, 1.6 );
    d.print();
}

Results are:

0.1,0.2,0.3,0.4,
1.3,1.4,1.5,1.6,
1.3,1.4,1.5,1.6,

Online code: https://rextester.com/MIF22368

  • Related