I don’t understand why in foobar
below I need to specify std::vector<int>{}
whereas in foobar2
I do not:
#include <iostream>
#include <memory>
#include <vector>
#include <tuple>
std::tuple<std::unique_ptr<int>, std::vector<int>> foobar() {
std::unique_ptr<int> test = std::make_unique<int>(42);
return { std::move(test), {} }; // <= this is a syntax error
// return { std::move(test), std::vector<int>{} } // <= this compiles...
}
std::tuple<int, std::vector<int>> foobar2() {
return { {}, {} };
}
int main() {
std::cout << *std::get<0>(foobar()) << "n";
std::cout << std::get<0>(foobar2()) << "n";
return 0;
}
The error message from GCC is
<source>: In function 'std::tuple<std::unique_ptr<int, std::default_delete<int> >, std::vector<int, std::allocator<int> > > foobar()':
<source>:8:34: error: could not convert '{std::move<unique_ptr<int>&>(test), <brace-enclosed initializer list>()}' from '<brace-enclosed initializer list>' to 'std::tuple<std::unique_ptr<int, std::default_delete<int> >, std::vector<int, std::allocator<int> > >'
8 | return { std::move(test), {} }; // <= this is a syntax error
| ^
| |
| <brace-enclosed initializer list>
Compiler returned: 1
1
1 Answer
Given a
template< class... Types >
class tuple;
There are two possible constructors that can be used here. The first one is:
tuple( const Types&... args );
However it can only be used if all tuple members are copy-constructible. The unique_ptr
is, of course, not copy-constructible.
This leaves only one other possible constructor:
template< class... UTypes >
tuple( UTypes&&... args );
That is, a forwarding constructor, a "Hail Mary" that forwards all its parameters to the constructor of each underlying tuple member.
{}
An empty braced-init list is typeless, and cannot be bound to a forwarding reference.
This could possibly work if only there was one more constructor:
tuple(Types && ... Args);
that participates in overload resolution if all member types are movable. Alas, there isn’t.
whereas in foobar2 I do not:
foobar2
‘s tuple members are copy-constructible. The first constructor overload gets used.
2
-
1
They added default template arguments for one of the
pair
constructors in C++23, which solves problems like this. Unfortunately, you can't default a pack in current C++23, so this solution isn't applicable to tuples. (But I hear there is a proposal.)– Brian Bi1 hour ago
-
@BrianBi I actually noticed that the naked {} version works with a pair and wondered about that too.
– jwezorek20 mins ago
Please add few tags like gcc, default-constructor, etc… for maximizing reach & better solution
18 hours ago