3

So I can do this:

for(const auto i : { 13, 42 }) cout << i << ' ';

But I can't do this:

copy_n(cbegin({ 13, 42 }), 2, ostream_iterator<int>(cout, " "));

It gives me the error:

error: no matching function for call to cbegin(<brace-enclosed initializer list>)

What is it about the for-statement that allows this but not the cbegin function?

Edit:

It appears that the problem is that my initializer_list isn't being treated as an initializer_list, because if I do this it works:

copy_n(cbegin(initializer_list<int>{ 13, 42 }), 2, ostream_iterator<int>(cout, " "));

Live Example

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    Isn't the range based `for` equivalent in usage to `std::begin()` instead of `std::cbegin()`? – UnholySheep May 17 '18 at 15:58
  • @UnholySheep I thought that was contingent on whether the iterating variable was `const`? – Jonathan Mee May 17 '18 at 17:04
  • I can't find any mention of that either on cppreference (https://en.cppreference.com/w/cpp/language/range-for) or the latest draft of the C++ standard (on the [github page](https://github.com/cplusplus/draft/blob/8ac95b9650ff8a796237fc4de1fdc537475074fa/source/statements.tex#L608)) - both only mention `begin` and `end`, nothing about `cbegin` or `cend`. – UnholySheep May 17 '18 at 17:25
  • @UnholySheep As I mention here: https://stackoverflow.com/questions/50395787/cant-define-an-initializer-list-in-cbegin?noredirect=1#comment87809805_50396019 whether `cbegin` or `begin` is called isn't really the point. They are effectively the same here. Using `begin` instead of `cbegin` doesn't solve the problem. – Jonathan Mee May 17 '18 at 17:31
  • 1
    It seems to do so on IDEOne: https://ideone.com/NczXM6 (I also tested it on MSVC2017). Mind you that I'm not sure *why* it does though, but I think that NathanOliver's answer gives the explanation? – UnholySheep May 17 '18 at 17:35
  • @NathanOliver This is UnholySheep's code on Coliru: http://coliru.stacked-crooked.com/a/72715aa5b2a37220 – Jonathan Mee May 17 '18 at 17:55
  • @JonathanMee OK. I deleted my answer as Yuki is right. there is now `cbegin` member for `std::initializer_list` – NathanOliver May 17 '18 at 17:56
  • @NathanOliver Is there? I don't see one? https://en.cppreference.com/w/cpp/utility/initializer_list furthermore I just thought `cbegin` called `begin` on a const container?!? – Jonathan Mee May 17 '18 at 17:59
  • 1
    @JonathanMee Sorry, typo, *now* should have been *no* – NathanOliver May 17 '18 at 18:00
  • I have a theory (but not enough knowledge of templates to validate it): Considering that `std::begin` is specialized for `initializer_list` (https://en.cppreference.com/w/cpp/utility/initializer_list/begin2) could it be that template type deduction fails (as in the deleted answer) and therefore `std::cbegin` does not work "normally"? And because there exists no specialization of `std::cbegin` for `initializer_list` (for some reason) the code fails to compile? – UnholySheep May 17 '18 at 18:19
  • @UnholySheep Hmmm... I don't think so. I think my brackets aren't correctly being treated as an `initializer_list`: http://coliru.stacked-crooked.com/a/8506ac8fe6ca6f36 – Jonathan Mee May 17 '18 at 18:26
  • @NathanOliver It looks like the problem is not that anything isn't implemented for an `initializer_list` but rather my brackets aren't interpreted as an `initializer_list`: http://coliru.stacked-crooked.com/a/8506ac8fe6ca6f36 – Jonathan Mee May 17 '18 at 18:27
  • @JonathanMee Very weird. I'm not sure what is causing this. – NathanOliver May 17 '18 at 18:29
  • @NathanOliver Is this using `begin`'s array constructor?!? I bet that's what it is, but I can't figure out how to validate... – Jonathan Mee May 17 '18 at 18:41
  • I tried making simple recreations of `cbegin` and when leaving out the specialization for `std::initializer_list` the template deduction fails (see here: http://coliru.stacked-crooked.com/a/7422641df3b274a7), but providing a specialization makes the code work as intended – UnholySheep May 17 '18 at 18:47
  • @JonathanMee I wouldn't think so. That said [this](http://coliru.stacked-crooked.com/a/980ac8eaf5eec060) doesn't work but if you change it to `std::begin` instead of `begin` in `main` it does. – NathanOliver May 17 '18 at 18:47
  • @JonathanMee `std::initializer_list` overload `std::begin`: http://en.cppreference.com/w/cpp/utility/initializer_list/begin2 That is why it works with `begin`. – NathanOliver May 17 '18 at 18:49
  • @NathanOliver Hmmm... Again... I don't feel like that's true. Cause if I explicitly construct an `initializer_list` `cbegin` works :/ – Jonathan Mee May 17 '18 at 19:03
  • @JonathanMee It works because now `cbegin` knows what the type is and `cbegin` forwards to `begin` and `begin` exists for `std::initializer_list`. – NathanOliver May 17 '18 at 19:04
  • See [this](http://coliru.stacked-crooked.com/a/c62c314bab90ec9a) example. If you comment out `begin(std::initializer_list il)` it will fail to compile. – NathanOliver May 17 '18 at 19:08
  • @NathanOliver So `cbegin` is implemented as: `template constexpr auto cbegin(const C& c) -> decltype(std::begin(c))` shouldn't that pick up the `begin(initializer_list)` in the `decltype`? – Jonathan Mee May 17 '18 at 19:25
  • 1
    Sure but it still can't determine what `C` is as `{13, 42}` doesn't have a type. – NathanOliver May 17 '18 at 19:26

2 Answers2

2

{ 13, 42 } is braced initializer list. It doesn't have a type, it is just a list and it depends on how it used as how it gets treated. std::cbegin is defined like

template< class C > 
constexpr auto cbegin( const C& c ) -> decltype(std::begin(c));

and since the braced initializer list doesn't have a type, the template type deduction fails. In the ranged based for loop we use the list different. A range based for loop expands to

{
    init-statement
    auto && __range = range_expression ; 
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) { 
        range_declaration = *__begin; 
        loop_statement 
    } 
} 

with auto && __range = range_expression ; becoming auto && __range = { 13, 42 }. Now auto follows template type deduction except that since the committee decide that auto should work with braced initializer list, auto will deduce { 13, 42 } to a std::initiaizer_list<int> since the list contains only ints.


If you change the code to

copy_n(begin({ 13, 42 }), 2, ostream_iterator<int>(cout, " "));

even though std::begin is defined like cbegin and takes a template type, <initializer_list> introduces an overload that takes a std::initializer_list and that will get called instead.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I think it is true that brace-enclosed list is not necessarily an initializer-list, it depends on the context, but I think in this case it is nothing else, but an initializer-list-type object. – Yuki May 17 '18 at 16:21
  • I was ready to accept this but then I noticed this: https://stackoverflow.com/questions/50395787/cant-define-an-initializer-list-in-cbegin?noredirect=1#comment87810453_50395787 Does this make sense? – Jonathan Mee May 17 '18 at 17:52
  • @JonathanMee IDEone is blocked for me. Could you share the code posted there with coliru? – NathanOliver May 17 '18 at 17:52
  • If you could edit your `cbegin` definition to `... -> decltype(std::begin(c))` I'd like to accept. – Jonathan Mee May 17 '18 at 19:38
1

Initializer list stores only pointers to the original list and by design it does not allow the user to modify the data, i.e.

const T* begin(); // returns a constant object

So the begin member or a begin free-function would be equivalent to cbegin. That is why only begin exists.

Update

Concerning cbegin(std::initializer_list<int>...: that works because you explicitly specified the type and cbegin(const Container&... is actually a template function, and begin(std::initializer_list<U>... is a different template function, as I said in the first part of the answer there is no cbegin(std::initializer_list<U>... function.

And the solving part is that you cannot do "chained" template deduction, it does not make sense: Container -> std::initializer_list<U> -> std::initializer_list<int>. Only one of the two deduction might exist, either Container -> std::initializer_list<int>, or std::initializer_list<U> -> std::initializer_list<int>

Yuki
  • 3,857
  • 5
  • 25
  • 43
  • 1
    This is simply irrelevent. `cbegin` calls the `begin` method on a const container: https://en.cppreference.com/w/cpp/iterator/begin This has absolutely no bearing on the problem. Please remove this misleading answer. – Jonathan Mee May 17 '18 at 17:10
  • Curses! Am I wrong? https://stackoverflow.com/questions/50395787/cant-define-an-initializer-list-in-cbegin?noredirect=1#comment87810453_50395787 – Jonathan Mee May 17 '18 at 17:50
  • You seem to be *exactly* right... but this is not in keeping with what how I'm reading the standard to say that `cbegin` should be implemented! – Jonathan Mee May 17 '18 at 17:58
  • I've updated my question, this *is* related to the problem, but this is not *the* problem. An actual `intializer_list` has no trouble with `cbegin`. – Jonathan Mee May 17 '18 at 18:32