Initialize std::tuple with classes which have two or more arguments
Asked Answered
F

2

12
#include <iostream>

class NoCopyMove {
public:
    NoCopyMove(int a) : a_(a), b_(a) {}
    NoCopyMove(int a, int b) : a_(a), b_(b) {}

    NoCopyMove(const NoCopyMove&) = delete;
    NoCopyMove& operator=(const NoCopyMove&) = delete;
    NoCopyMove(NoCopyMove&&) = delete;
    NoCopyMove& operator=(NoCopyMove&&) = delete;

    int a_;
    int b_;
};

int main()
{
    std::tuple<NoCopyMove, NoCopyMove> t {6, 9};
    std::cout << std::get<0>(t).a_ << std::endl;   
    std::tuple<NoCopyMove, NoCopyMove> t2 {{6, 7}, {8, 9}};
    return 0;
}

I'm trying to make a tuple of classes that has more than 2 arguments as their constructor. If there is just one constructor argument it works.

main.cpp:45:28: error: no matching constructor for initialization of 'std::tuple<NoCopyMove>'
    std::tuple<NoCopyMove> t2 {{6, 7}, {8, 9}}};
                           ^  ~~~~~~~~~~~~~~~~

Probably some kind of hint to the compiler would be needed but I have no idea how I could do that. Any kind of keyword and hint will be appreciated.

Foramen answered 28/2, 2023 at 8:30 Comment(5)
A side note: I think you have a typo: {8, 9}}}; should be {8, 9}}; (i.e. only 2 }).Candelariacandelario
Also from the compiler error it seems you are instantiating a tuple with only one element of type NoCopyMove.Schroeder
Tuple is not an aggregate, so it can't be initialized using aggregate initialization, and when you look at expression {{6, 7}, {8, 9}} - it probably have type something like std::initializer_list<std::initializer_list<int>> or something similar which doesn't match nor tuple nor your constructor.Swithin
Just one excessive closing brace. Remove it, and report back please.Auraaural
Side note: purpose of tuple is to store some data for later in generic programing. IMPO using tuples outside of templates makes code harder to read and maintain. So from that point of view if you have some generic code which needs keep data for later use, then your class you have problem with (NoCopyMove) do not meet requirements of this generic code. If you do not have generic code, then define struct which will be tailored to store and construct NoCopyMove with multiple arguments.Wane
S
12

Aside from the extraneous closing brace, there is no way you can construct a tuple of uncopyable and immoveable types like this. std::tuple does not support piecewise or 'emplace-style' construction and as mentioned in the comments it is also not an aggregate, so it needs to copy or move the individual elements in place. The constructor you would expect to be chosen here is the one which takes each type in the tuple by const & and quoting cppreference:

This overload participates in overload resolution only if sizeof...(Types) >= 1 and std::is_copy_constructible::value is true for all i.

However your types are not copy constructible, so you are out of luck if this really is what you need to do.

Schroeder answered 28/2, 2023 at 9:3 Comment(0)
H
10
std::tuple<NoCopyMove, NoCopyMove> t2 {{6, 7}, {8, 9}}};

is actually

std::tuple<NoCopyMove, NoCopyMove> t2 {NoCopyMove{6, 7}, NoCopyMove{8, 9}}};

and so requires move or copy constructor.

In C++17, with mandatory copy elision, with extra layer

template <typename... Ts>
struct Args
{
    Args(Ts... args) : tuple{std::forward<Ts>(args)...} {}

    template <typename T>
    operator T() && { return std::make_from_tuple<T>(std::move(tuple)); }

private:
    std::tuple<Ts...> tuple;
};

template <typename... Ts>
Args(Ts&&...) -> Args<Ts&&...>;

you might do:

std::tuple<NoCopyMove, NoCopyMove> t2 {Args{6, 7}, Args{8, 9}}};

Demo

And so you construct in place NoCopyMove from Args{..}.

Heeley answered 28/2, 2023 at 15:42 Comment(3)
Note that this question is tagged C++14, but this answer requires at least C++17 for make_from_tuple, CTAD, and mandatory prvalue copy elision.Nels
@MilesBudnek: indeed. whereas make_from_tuple can be reimplemented, and CTAD replaced by makeArgs, there are no replacement for mandatory prvalue copy elision :/Heeley
Could you explain more precisely why the copy elision kicks in? std::tuple<SomeStruct> t{SomeStruct{someArgs...)} gives 2 objects: the one in the tuple and the temporary. std::tuple<SomeStruct> t{Args{someArgs...)} gives only one object. Isn't the conversion constructing also a SomeStruct temporary?Faunia

© 2022 - 2024 — McMap. All rights reserved.