Search code examples
c++templates

How does template type deduction work when multiple parameters make use of the same template type? (C++20)


For example, the following code block fails to compile:

template <typename T>
void WithOptional(T o1, std::optional<T> o2) {}

int main()
{
    WithOptional(1, 1);
}

Here is a snippet of the compiler error:

could not match 'std::optional' against 'int'

On the other hand, this code does compile:

template <typename T>
void WithVector(T o1, std::vector<T> o2) {}

int main()
{
    WithVector(1, {1});
}

I'm a bit confused on the discrepancy here. In both cases, the second argument has a type different from the parameter: for the first block, I pass in an int instead of an std::optional and in the second case, a std::initializer_list instead of a std::vector. Why does the second code snippet compile?


Solution

  • {1} is not a std::initializer_list. It's not of any type at all: {1} is not an expression. {1} is, effectively, a special part of the function call syntax, and is handled specially by deduction. Specifically, an braced-init-list used as an argument does not contribute to template argument deduction unless the function parameter is already declared to be a std::initializer_list, reference thereof, or an array.

    So, in the call WithVector(1, {1}), WithVector's T can only be deduced from the expression 1, as the second parameter is declared as neither a std::initializer_list nor an array. T is successfully deduced to int, and then that reveals that {1} should be used to initialize a std::vector<int>.

    (I don't remember the reference for the quote, but the committee's intent with braced-init-lists was to have them look like expressions but not be expressions and instead be special convenience syntax for use under certain constructs, such as function calls and return statements. IMO that was certainly a choice.)