Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto with uniform initialization expands to unexpected type

Consider this short program compiled with GCC 4.7.2 g++ -std=c++11 test.cc

#include <memory>
#include <queue>

struct type{
  type(int a) : v(a) {}
  int v;
};

typedef std::shared_ptr<type> type_ptr;

int main(){
  int value = 3;
  std::queue<type_ptr> queue;
  auto ptr{std::make_shared<type>(value)};
  queue.push(ptr);
}

The compiler outputs the following errors:

src/test.cc: In function ‘int main()’:
src/test.cc:15:17: error: no matching function for call to ‘std::queue<std::shared_ptr<type> >::push(std::initializer_list<std::shared_ptr<type> >&)’
src/test.cc:15:17: note: candidates are:
In file included from /usr/include/c++/4.7/queue:65:0,
                 from src/test.cc:2:
/usr/include/c++/4.7/bits/stl_queue.h:211:7: note: void std::queue<_Tp, _Sequence>::push(const value_type&) [with _Tp = std::shared_ptr<type>; _Sequence = std::deque<std::shared_ptr<type>, std::allocator<std::shared_ptr<type> > >; std::queue<_Tp, _Sequence>::value_type = std::shared_ptr<type>]
/usr/include/c++/4.7/bits/stl_queue.h:211:7: note:   no known conversion for argument 1 from ‘std::initializer_list<std::shared_ptr<type> >’ to ‘const value_type& {aka const std::shared_ptr<type>&}’
/usr/include/c++/4.7/bits/stl_queue.h:216:7: note: void std::queue<_Tp, _Sequence>::push(std::queue<_Tp, _Sequence>::value_type&&) [with _Tp = std::shared_ptr<type>; _Sequence = std::deque<std::shared_ptr<type>, std::allocator<std::shared_ptr<type> > >; std::queue<_Tp, _Sequence>::value_type = std::shared_ptr<type>]
/usr/include/c++/4.7/bits/stl_queue.h:216:7: note:   no known conversion for argument 1 from ‘std::initializer_list<std::shared_ptr<type> >’ to ‘std::queue<std::shared_ptr<type> >::value_type&& {aka std::shared_ptr<type>&&}’

Indicating that the auto type is expanded to an initializer list instead of std::shared_ptr<type>; in fact replacing {...} with = ... makes the code compile as auto expands to the correct type.

I'm a bit suprised that this seemingly obvious use case fails to achieve the expected result. Especially as I recall the new bracket initialization syntax to be touted as the end-all, be-all solution to initializing problems.

So my question is: Was this intended in the standard? Or is it an oversight or even a gcc bug? Or am I just thinking about it wrong?

like image 484
Emily L. Avatar asked Dec 15 '22 08:12

Emily L.


1 Answers

As Xeo says in his comment, this is standard behavior. 7.1.6.4 auto specifier [dcl.spec.auto] para 6 specifies:

Once the type of a declarator-id has been determined according to 8.3, the type of the declared variable using the declarator-id is determined from the type of its initializer using the rules for template argument deduction. Let T be the type that has been determined for a variable identifier d. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>. The type deduced for the variable d is then the deduced A determined using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the initializer for d is the corresponding argument. If the deduction fails, the declaration is ill-formed.

It is also widely despised - there's a proposal under review by the committee to change the behavior for C++14. C++14's support for generalized lambda capture exacerbates the problem.

Update: In Urbana (See CWG Motion 16 in N4251 WG21 2014-11 Urbana Minutes) the committee applied N3922 New Rules for auto deduction from braced-init-list to the C++17 Working Paper. They decided to fix the special case that allows auto to deduce an initializer_list by adding another special case. auto works the same way for copy-list-initialization, but for direct-list-initialization from a braced-init-list with a single element auto deduces from that element directly. direct-list-initialization from a multiple-element braced-init-list is now ill-formed.

That means that given

auto x = {42};

x has type std::initializer_list<int>, but in

auto x{42};

x is an int.

like image 69
Casey Avatar answered Feb 19 '23 03:02

Casey