4

Consider class A with the only custom constructor:

class A
{
public:
    A(float) {}

private:
    A() = delete;
    A(const A&) = delete;
    A(A&&) = delete;
};

And another class B, which contains a tuple of A (let it be the only tuple member for simplicity):

class B
{
public:
    B() : ta(0.0f) {} // ta initialization OK

private:
    std::tuple<A> ta;
};

Now we can declare an object of B and it works fine:

B b;

But how to do the same if the constructor of A has more than one argument?

class A
{
public:
    A(float, int) {}

private:
    A() = delete;
    A(const A&) = delete;
    A(A&&) = delete;
};

class B
{
public:
//  B() : ta(0.0f, 1) {} // Compilation errors
//  B() : ta({0.0f, 1}) {} // Compilation errors
//  B() : ta{0.0f, 1} {} // Compilation errors
//  B() : ta(A(0.0f, 1)) {} // No constructor to copy or move A

private:
    std::tuple<A> ta;
};

B b;

std::make_tuple, std::forward_as_tuple and others like that don't solve the problem, because default, copy and move constructors of A are disabled.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Sergey
  • 7,985
  • 4
  • 48
  • 80

1 Answers1

3

std::tuple has few facilities for constructing its members in-situ. The generally expected way to initialize a tuple is via copy/move from its component objects.

It does permit implicit conversion of constructor parameters to their respective arguments. But that is only a 1:1 relationship between parameters and tuple members. There is no way to have a many-to-one relationship between constructor parameters and tuple members.

You can allow your type to be implicitly constructible from a tuple itself:

class A
{
public:
    A(float, int) {}
    A(const std::tuple<float, int> &tpl)
        : A(std::get<0>(tpl), std::get<1>(tpl)) {}

private:
    A() = delete;
    A(const A&) = delete;
    A(A&&) = delete;
};

Thus, you can construct a tuple like this:

class B
{
public:
    B() : ta(std::tuple<float, int>(0.0f, 1)) {}

private:
    std::tuple<A> ta;
};

If you want to employ deeper metaprogramming, you may yet be able to make a set of constructors that allow your type to be constructed from a tuple whose types match any available constructors:

class A
{
public:
    A(float, int) {}

    template<typename ...Args>
    A(const std::tuple<Args...> &args) : A(args, std::index_sequence_for<Args...>{}) {}

private:
    A() = delete;
    A(const A&) = delete;
    A(A&&) = delete;

    template<typename ...Args, std::size_t ...Is>
    A(const std::tuple<Args...> &args, std::index_sequence<Is...>) 
        : A(std::get<Is>(args)...) {}
};
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982