Braced-init-lists and function template type deduction order
Asked Answered
K

1

6

I have a question regarding the function template parameter type deduction procedure.

Take this example:

#include <vector>
#include <sstream>
#include <string>
#include <iterator>
#include <fstream>

int main()
{
    std::ifstream file("path/to/file");
    std::vector<int> vec(std::istream_iterator<int>{file},{}); // <- This part
    return 0;
}

If I understand things correctly, the second parameter is deduced to be of type std::istream_iterator of which the default constructor is called.

The appropriate std::vector constructor is declared as:

template <class InputIterator>
         vector (InputIterator first, InputIterator last,
                 const allocator_type& alloc = allocator_type());

Since the first parameter type is deduced as std::istream_iterator<int> the second parameter is deduced as std::istream_iterator<int> too and so the uniform initialization semantics can be applied. What I have no idea about is at what order the type deduction happens. I would really appreciate some info on this.

Thanks in advance!

Kantianism answered 5/6, 2014 at 12:3 Comment(1)
Does it work? If so the first one must be deducted first.Watford
C
9

Let's use an even simpler example:

template<class T>
void foo(T, T);

foo(42, {});

The function call has two arguments:

  • a prvalue expression of type int (an integer literal)
  • a braced-init-list {}

The latter, {}, can be part of an expression-list but it is not an expression itself. An expression-list is defined as an initializer-list. braced-init-lists do not have a type.

Template type deduction is done for each function parameter individually [temp.deduct.type]/2. [temp.deduct.call]/1 states about type deduction for a function parameter P:

If removing references and cv-qualifiers from P gives std::initializer_list<P'> for some P' and the argument is an initializer list, then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context. [emphasis mine]

So in the call foo(42, {}); the T will not be deduced from the second argument {}. However, T can be deduced from the first argument.

In general, we can deduce T from multiple function parameters. In that case, the deduced types have to match exactly [temp.deduct.type]/2. There is no problem if the type is only deduced from one function parameter but used elsewhere (in another function parameter that is in a non-deduced context, in the return type etc). Type deduction can fail e.g. when a template parameter cannot be deduced from any function parameter and is not set explicitly.

After deduction, T will be substituted by int, producing a function signature similar to:

void foo<int>(int, int);

This function can be called with the two arguments 42 and {}. The latter will perform a copy-list-initialization leading to a value-initialization of the second parameter.

Callen answered 5/6, 2014 at 12:31 Comment(1)
This is more or less how I was seeing it intuitively. Thanks for clearing things up.Kantianism

© 2022 - 2024 — McMap. All rights reserved.