Consider the following code:
#include <utility>
#include <vector>
using V = std::vector<int>;
int main() {
std::pair<int, V> p1{1, 2}; // p1.second has 2 elements
std::pair<int, V> p2{1, {2}}; // p2.second has 1 element
std::pair<V, V> p3{2, 2}; // Both vectors have 2 elements
std::pair<V, V> p4{{2}, {2}}; // Both vectors have 1 element
std::pair<V, V> p5{2, {2}}; // Does not compile
// p5.first should have 2 elements, while the other should have 1
}
My main issue is with the last line, p5, which does not compile with g++-12 but does with g++-10. I would like to know:
- What has changed that caused this issue?
- Can it be compiled again without having to build the vectors and
copying them in (i.e. not use
V(2)somewhere)
I have tried playing with std::piecewise_construct as well but I'm not sure it is the correct solution here.
ERROR:
<source>: In function 'int main()':
<source>:9:30: error: no matching function for call to 'std::pair<std::vector<int>, std::vector<int> >::pair(<brace-enclosed initializer list>)'
9 | std::pair<V, V> p3{2, {2}};
| ^
In file included from /opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/utility:69,
from <source>:1:
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:354:9: note: candidate: 'template<class _U1, class _U2> requires _S_constructible<_U1, _U2>() && _S_dangles<_U1, _U2>() constexpr std::pair<_T1, _T2>::pair(std::pair<_U1, _U2>&&) [with _U2 = _U1; _T1 = std::vector<int>; _T2 = std::vector<int>]' (deleted)
354 | pair(pair<_U1, _U2>&&) = delete;
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:354:9: note: template argument deduction/substitution failed:
<source>:9:30: note: mismatched types 'std::pair<_T1, _T2>' and 'int'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:345:9: note: candidate: 'template<class _U1, class _U2> requires _S_constructible<_U1, _U2>() && !_S_dangles<_U1, _U2>() constexpr std::pair<_T1, _T2>::pair(std::pair<_U1, _U2>&&) [with _U2 = _U1; _T1 = std::vector<int>; _T2 = std::vector<int>]'
345 | pair(pair<_U1, _U2>&& __p)
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:345:9: note: template argument deduction/substitution failed:
<source>:9:30: note: mismatched types 'std::pair<_T1, _T2>' and 'int'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:339:9: note: candidate: 'template<class _U1, class _U2> requires _S_constructible<const _U1&, const _U2&>() && _S_dangles<const _U1&, const _U2&>() constexpr std::pair<_T1, _T2>::pair(const std::pair<_U1, _U2>&) [with _U2 = _U1; _T1 = std::vector<int>; _T2 = std::vector<int>]' (deleted)
339 | pair(const pair<_U1, _U2>&) = delete;
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:339:9: note: template argument deduction/substitution failed:
<source>:9:30: note: mismatched types 'const std::pair<_T1, _T2>' and 'int'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:330:9: note: candidate: 'template<class _U1, class _U2> requires _S_constructible<const _U1&, const _U2&>() && !_S_dangles<_U1, _U2>() constexpr std::pair<_T1, _T2>::pair(const std::pair<_U1, _U2>&) [with _U2 = _U1; _T1 = std::vector<int>; _T2 = std::vector<int>]'
330 | pair(const pair<_U1, _U2>& __p)
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:330:9: note: template argument deduction/substitution failed:
<source>:9:30: note: mismatched types 'const std::pair<_T1, _T2>' and 'int'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:323:9: note: candidate: 'template<class _U1, class _U2> requires _S_constructible<_U1, _U2>() && _S_dangles<_U1, _U2>() constexpr std::pair<_T1, _T2>::pair(_U1&&, _U2&&) [with _U2 = _U1; _T1 = std::vector<int>; _T2 = std::vector<int>]' (deleted)
323 | pair(_U1&&, _U2&&) = delete;
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:323:9: note: template argument deduction/substitution failed:
<source>:9:30: note: couldn't deduce template parameter '_U2'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:315:9: note: candidate: 'template<class _U1, class _U2> requires _S_constructible<_U1, _U2>() && !_S_dangles<_U1, _U2>() constexpr std::pair<_T1, _T2>::pair(_U1&&, _U2&&) [with _U2 = _U1; _T1 = std::vector<int>; _T2 = std::vector<int>]'
315 | pair(_U1&& __x, _U2&& __y)
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:315:9: note: template argument deduction/substitution failed:
<source>:9:30: note: couldn't deduce template parameter '_U2'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:238:9: note: candidate: 'template<class ... _Args1, long unsigned int ..._Indexes1, class ... _Args2, long unsigned int ..._Indexes2> constexpr std::pair<_T1, _T2>::pair(std::tuple<_Args1 ...>&, std::tuple<_Args2 ...>&, std::_Index_tuple<_Indexes1 ...>, std::_Index_tuple<_Indexes2 ...>) [with _Args1 = {_Args1 ...}; long unsigned int ..._Indexes1 = {_Indexes1 ...}; _Args2 = {_Args2 ...}; long unsigned int ..._Indexes2 = {_Indexes2 ...}; _T1 = std::vector<int>; _T2 = std::vector<int>]'
238 | pair(tuple<_Args1...>&, tuple<_Args2...>&,
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:238:9: note: template argument deduction/substitution failed:
<source>:9:30: note: mismatched types 'std::tuple<_UTypes ...>' and 'int'
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:202:9: note: candidate: 'template<class ... _Args1, class ... _Args2> constexpr std::pair<_T1, _T2>::pair(std::piecewise_construct_t, std::tuple<_Args1 ...>, std::tuple<_Args2 ...>) [with _Args1 = {_Args1 ...}; _Args2 = {_Args2 ...}; _T1 = std::vector<int>; _T2 = std::vector<int>]'
202 | pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>);
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:202:9: note: template argument deduction/substitution failed:
<source>:9:30: note: candidate expects 3 arguments, 2 provided
9 | std::pair<V, V> p3{2, {2}};
| ^
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:305:7: note: candidate: 'constexpr std::pair<_T1, _T2>::pair(const _T1&, const _T2&) requires _S_constructible<const _T1&, const _T2&>() [with _T1 = std::vector<int>; _T2 = std::vector<int>]'
305 | pair(const _T1& __x, const _T2& __y)
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:305:23: note: no known conversion for argument 1 from 'int' to 'const std::vector<int>&'
305 | pair(const _T1& __x, const _T2& __y)
| ~~~~~~~~~~~^~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:249:7: note: candidate: 'constexpr std::pair<_T1, _T2>::pair() requires (is_default_constructible_v<_T1>) && (is_default_constructible_v<_T2>) [with _T1 = std::vector<int>; _T2 = std::vector<int>]'
249 | pair()
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:249:7: note: candidate expects 0 arguments, 2 provided
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:198:17: note: candidate: 'constexpr std::pair<_T1, _T2>::pair(std::pair<_T1, _T2>&&) [with _T1 = std::vector<int>; _T2 = std::vector<int>]'
198 | constexpr pair(pair&&) = default; ///< Move constructor
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:198:17: note: candidate expects 1 argument, 2 provided
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:197:17: note: candidate: 'constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = std::vector<int>; _T2 = std::vector<int>]'
197 | constexpr pair(const pair&) = default; ///< Copy constructor
| ^~~~
/opt/compiler-explorer/gcc-trunk-20230315/include/c++/13.0.1/bits/stl_pair.h:197:17: note: candidate expects 1 argument, 2 provided
Compiler returned: 1
This looks like a bug fix (for C++20 and below). There is no properly-implemented version of C++ where this:
Would compile.
A braced-init-list/initializer list (note the lack of an "_") is not an expression; it is its own separate grammatical construct. The way it participates in template argument deduction is really very simple.
It doesn't (mostly).
If the corresponding parameter is explicitly an
initializer_list<E>of some sort, then it can deduceE. Otherwise it cannot and the parameter is non-deduced:This is repeated:
And, of course, if a template parameter can't be deduced, then template argument deduction fails, and that function cannot be called.
This means that constructors like this:
Will not work on braced-init-lists. Therefore, the only viable constructors for
{2, {2}}are ones where the second parameter is not a template parameter that needs deduction.Which basically means this one:
pair( const T1& x, const T2& y );
T1andT2are from the class template parameters, not those of the function template. You already specified them asVandV, so that tries to be called.However,
vector<int>is not implicitly convertible from an integer 2. The constructor that takes a single integer isexplicit, so attempting to initializexwill fail.This constructor has been explicit since C++98. So
std::pair<V, V> p5{2, {2}};should never have worked. If it did, this was a bug in the implementation.Note that C++23 changes one of
pairs constructors to have default template parameters:This would allow the
{2, {2}}syntax to work, as it no longer relies on template argument deduction to get template parameter types.This never worked. It always did a copy.
You could use
piecewise_constructandinitializer_listgymnastics to avoid a "copy". But really, just switch to using atupleand use the type properly in the initializer list:Or, to cut down on redundancy:
This will move from the parameters, not copy from them.
The latter works because it calls the
T1, T2constructor, which can implicitly convert from a braced-init-list into avector.The former works because the
U1, U2constructor will deduce these as two integers. And avectoris constructible (but not implicitly convertible) from an integers. So the twoVs can be constructed by direct initialization from those parameters.The asymmetric one doesn't work because the presence of a braced-init-list shuts down the
U1, U2constructor entirely, sinceU2cannot be deduced (again, until C++23).