C++ puzzle #4
Puzzle #4 (based on C++17)
#include <tuple>
#include <utility>
#include <functional>
#include <iostream>
template <typename A, typename B>
struct foo
{
foo(A, B) {};
};
template <typename A, typename B>
auto make_foo(A&& a, B&& b)
{
return foo{ std::forward<A>(a), std::forward<B>(b) };
}
int main()
{
decltype(auto) a = 0;
decltype(auto) b = std::ref(a);
auto f1 = foo(a, b);
auto f2 = make_foo(a, b);
auto p1 = std::pair(a, b);
auto p2 = std::make_pair(a, b);
auto t1 = std::tuple(a, b);
auto t2 = std::make_tuple(a, b);
std::cout << std::is_same_v<decltype(f1), decltype(f2)>;
std::cout << std::is_same_v<decltype(p1), decltype(p2)>;
std::cout << std::is_same_v<decltype(t1), decltype(t2)>;
}
With given code and C++17 compiler, pick one answer:
- Guaranteed to print: 000.
- Guaranteed to print: 001.
- Guaranteed to print: 010.
- Guaranteed to print: 011.
- Guaranteed to print: 100.
- Guaranteed to print: 101.
- Guaranteed to print: 110.
- Guaranteed to print: 111.
- Guaranteed to print something else.
- Undefined behavior.
- Implementation defined.
- Will not compile.
Click here for the answer
The correct answer is: Guaranteed to print 100.
Purpose of this puzzle is to explain “why we can’t get rid of make_pair and make_tuple functions? Since C++17 we have deduction guides for classes”.
Deduction guides would be perfectly enough in most of the cases. make_pair and make_tuple are useful when we’re dealing with reference_wrappers but we want to have e.g. pair of references, not reference_wrappers.
make_pair and make_tuple are able to recognize such situation and instead of creating e.g. pair<reference_wrapper<int>, reference_wrapper<int>>
it’ll return pair<int&, int&>
.
Of course there are use cases when we want to have pair with reference_wrapper[s]. Then we always can explicitly say it to the compiler, e.g. by using mentioned deduction guides.
Back to the puzzle. Such types will be deduced by compiler:
decltype(auto) a = 0; // int
decltype(auto) b = std::ref(a); // std::reference_wrapper<int>
auto f1 = foo(a, b); // foo<int, std::reference_wrapper<int>>
auto f2 = make_foo(a, b); // foo<int, std::reference_wrapper<int>>
auto p1 = std::pair(a, b); //std::pair<int, std::reference_wrapper<int>>
auto p2 = std::make_pair(a, b); // std::pair<int, int&>
auto t1 = std::tuple(a, b); //std::tuple<int, std::reference_wrapper<int>>
auto t2 = std::make_tuple(a, b); // std::tuple<int, int&>
As you can see p1, p2 and t1, t2 have different types. That’s why two last is_same_v print zeros (false).