Reputation: 536
This code works as expected:
#include <iostream>
#include <tuple>
int main() {
std::tuple<int> t1{3};
std::tuple<int> t2{t1};
std::cout << std::get<0>(t2) << std::endl;
return 0;
}
It correctly prints the number 3. But this code doesn’t:
#include <any>
#include <iostream>
#include <tuple>
int main() {
std::tuple<int> t1{3};
std::tuple<std::any> t2{t1};
std::cout << std::any_cast<int>(std::get<0>(t2)) << std::endl;
return 0;
}
std::any_cast<int>
throws the exception std::bad_any_cast
. However, if I add one more element to the tuples it does work:
#include <any>
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, float> t1{3, 3.14};
std::tuple<std::any, std::any> t2{t1};
std::cout << std::any_cast<int>(std::get<0>(t2)) << std::endl;
return 0;
}
I compiled everything with C++23.
I inspected the type of std::get<0>(t2)
:
#include <any>
#include <iostream>
#include <tuple>
int main() {
std::tuple<int> t1{3};
std::tuple<std::any> t2{t1};
std::cout << std::get<0>(t2).type().name() << std::endl;
return 0;
}
Compiling with Clang on Linux gave me the output St5tupleIJiEE
, which the LLVM demangler shows as std::tuple<int>
. So what is happening is that the tuple t1
is being copied to the std::any
inside t2
, while what I expected was the tuple itself t1
to be copied and the int
going into std::any
. This only happens when using a unary tuple with std::any
, otherwise the behavior is as expected.
The cppreference page for the constructors of std::tuple
has the following key constructors:
tuple( const Types&... args ); // (2)
template< class... UTypes >
constexpr tuple( tuple<UTypes...>& other ); // (4)
template< class... UTypes >
tuple( const tuple<UTypes...>& other ); // (5)
It seems that the unary tuple t2
is being constructed with (2) instead of (4) or (5), while the tuple with two elements is being constructed with (4) or (5).
So I have two questions
t2
and then use the assignment operator to copy t1
into it, but I don’t want to do that since I might want to use std::move
for performance but the code fails even in this case.Upvotes: 8
Views: 450
Reputation: 474266
tuple
has a bunch of constructors which can overlap in their meaning. There are priorities between them as to when which one gets called.
But a core rule of C++ is this: if you have a value of type T
, and you create a new type of exactly the type T
from that value, that process will invoke the copy (or move) constructor.
Your first example does that: t1
and t2
are the same type, so a copy happens.
Your second example does not. t2
is a completely different type from t1
. Therefore, the copy constructor doesn't get used; instead various tuple
constructors are considered.
There is a tuple
constructor which allows creating a tuple<Ts>
from a tuple<Us>
, so long as Ts
and Us
have the same number of types and that each T
is implicitly convertible from the corresponding U
.
However... that isn't the case here. std::any
is not implicitly convertible from an int
(or most types). So this constructor is not considered.
So what is considered is the construction of each element of the tuple
from each parameter in the constructor call. Since you're using direct-list-initialization, explicit
cosntructors are considered. And since there is an explicit conversion from std::tuple<int>
(the type of the first parameter) to the type of the first member of the tuple (ie: std::any
), that's what gets used.
How can I get around this issue?
When playing with any
, you have to be careful of explicit conversions. If you want to store an int
inside the any
inside a tuple
, you need to make this unambiguous. Pass exactly an int
explicitly by get
ing the int
out of t1
.
Upvotes: 4
Reputation: 545
As @NathanOliver comments, you are trying to use converting operator (5), which requires std::tuple_size > 1
.
You can work around this issue by constructing the second tuple from the first's argument: std::tuple<std::any> t2{ std::get<0>(t1) };
.
If you are working in some generic code which needs to handle tuple with any number of std::any
, then you need a slightly unwieldy:
auto t2 = std::apply([](auto&& args) {
return std::make_tuple(std::any{ std::forward<decltype(args)>(args) }...);
}, t1);
Upvotes: 6