Take for example the code snippet bellow that instantiates some tuple instances using the std::make_tuple function and the tuple constructor:

...

const auto integer = 0;
const auto text = std::string{};

/* tuple instance constructed using std::make_tuple with implicit type deduction */
const auto tuple_instance_with_implicit_make = std::make_tuple(integer, text);   // OK
    
/* tuple instance constructed using std::make_tuple with explicitly specified types */
const auto tuple_instance_with_explicit_make = std::make_tuple<int, std::string>(integer, text); // ERROR
    
/* tuple instance using tuple constructor with explicitly specified types */
const auto tuple_instance_explicit = std::tuple<int, std::string>(integer, text); // OK

/* tuple instance using tuple constructor with implicit type deduction */
const auto tuple_instance_implicit = std::tuple(integer, text);                   // OK
    
...

When I run the code I get the following error for the case using the std::make_tuple function with explicitly specified types but not for the case when using the tuple constructor directly with explicitly specified types:

 error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'const int'
   12 |     const auto tuple_instance_with_explicit_make = std::make_tuple<int, std::string>(integer, text); // ERROR
      |                                                                                      ^~~~~~~
In file included from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/uses_allocator_args.h:38,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/memory_resource.h:41,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/string:58,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/locale_classes.h:40,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/ios_base.h:41,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/ios:44,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/ostream:40,
                 from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/iostream:41,
                 from <source>:1:
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/tuple:1987:27: note:   initializing argument 1 of 'constexpr std::tuple<typename std::__strip_reference_wrapper<typename std::decay<_Elements>::type>::__type ...> std::make_tuple(_Elements&& ...) [with _Elements = {int, __cxx11::basic_string<char, char_traits<char>, allocator<char> >}]'
 1987 |     make_tuple(_Elements&&... __args)
      |                ~~~~~~~~~~~^~~~~~~~~~

Since both the std::make_tuple function and tuple constructor use forwarding references for their parameters and std::make_tuple forwards the passed arguments to the tuple constructor I would have expected to have the same behavior. As far as I know forwarding references can bind to objects and expressions of any value category be it lvalues or rvalues.

Can someone help me understand this behavior? Why creating a tuple instance using the constructor with explicitly specified types works but the one using std::make_tuple in the same manner does not?

0

There are 0 answers