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).

Thanks for reading o/