简体   繁体   中英

In C++11, can I implement an aggregate-type-initialization like constructor for an non-aggregate type, and how?

Say, I have a 1-dimensional array encapsulated in a class named stA

class stA
{
public:
    template<typename ... T>
    stA(T ... t):
        data_{t...}
    {}

private:
    int data_[2];
};

By taking advantage of variadic template , I can succeed in implementing this idea.

stA a = {1, 2};

But, when I tried to bring this trick to 2-dimensional-like class named stB ,

class stB
{
public:
    template<typename ... T>
    stB(T ... t):
        data_{t...}
    {}

private:
    stA data_[2];
};

the trick failed.

stB b = {{1, 2}, {1, 2}};

error: could not convert '{{1, 2}, {1, 2}}' from brace-enclosed initializer list to 'stB' stB b = {{1, 2}, {1, 2}};

And this quite confuses to me at beginning.

Because if template-argument-deduction in stB::Ctor results in T = const stA & , then the Ctor would become something like this,

stB(const stA & a, const stA & b):
        data_{a, b}
    {}

and of course stB b = {{1, 2}, {1, 2}}; would survive but with losing some flexibility in arguments.

After I had done some searching, I finally realized this may violate a rule of template argument deduction ,

Non-deduced contexts

6) The parameter P, whose A is a braced-init-list, but P is not std::initializer_list, a reference to one (possibly cv-qualified), or a reference to an array:

But I'm still wandering Is there still a way to get this idea happened and how?

The problem is that {1, 2} is not an expression and only expressions can be deduced.

Option 1: use aggregate initialization. Make the data_ member public and aggregate initialization allows you to initialize the elements. Drawback: little control.

Option 2: use initializer_list . As in:

class stA {
public:
    stA(std::initializer_list<int> init):
        data_{init.begin()[0], init.begin()[1]}
    {
        assert(init.size() == 2);
    }

private:
    int data_[2];
};

class stB {
public:
    stB(std::initializer_list<std::initializer_list<int>> init):
        data_{init.begin()[0], init.begin()[1]}
    {
        assert(init.size() == 2);
    }

private:
    stA data_[2];
};

Drawback: the size information is lost at compile time.

Option 3: use an aggregate type as constructor parameter.

class stA {
public:
    stA(const int (&arr)[2]):
        data_{arr[0], arr[1]}
    {
    }

private:
    int data_[2];
};

class stB {
public:
    stB(const int (&arr)[2][2]):
        data_{arr[0], arr[1]}
    {
    }

private:
    stA data_[2];
};

(This requires an extra pair of braces, as in stB b = {{{1, 2}, {3, 4}}}; .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM