Учитывая этот код:
#include <array>
#include <algorithm>
#include <initializer_list>
struct P {
int r = 1;
int g = 2;
int b = 3;
P() = default;
P(std::initializer_list<int> l) {}
P(int i){}
};
int main() {
std::array<P, 4> array{};
std::ranges::fill(array, P{}); // works
P p = {}; // also works
std::ranges::fill(array, {}); // why isn't P inferred?
}
Первая строка работает, потому что я каждый раз явно создаю новую P{}
.
Вторая строка работает, и я думаю, это потому, что список инициализаторов может быть неявно преобразован в P, даже если explicit
присутствовал во втором векторе. Но я не уверен, что это причина.
Третья строка не работает, но вторая указывает мне, что так и должно быть. Почему нет? Разве тип array
в качестве первого параметра не должен fill
давать компилятору достаточную информацию о том, что функция fill
имеет тип P, и иметь возможность использовать второй ctor?
Проблема здесь в том, что {}
— это не выражение с типом, это грамматическая конструкция, значение которой в определенных случаях определяет компилятор.
ranges::fill
раньше объявлялся примерно так:
template<class T, output_range<const T&> R>
constexpr borrowed_iterator_t<R> fill(R&& r, const T& value);
Когда вы вызываете fill(array, {})
, мы не можем вывести T
из {}
, поэтому это просто невозможно. Чтобы этот вызов работал, вам нужно добавить в функцию некоторые украшения, чтобы она знала, что делать.
Что-то вроде этого:
template<class R, class T = range_value_t<R>>
requires output_range<R, const T&>
constexpr borrowed_iterator_t<R> fill(R&& r, const T& value);
Теперь fill(array, {})
все еще не может вывести T
(поскольку {}
все еще не имеет типа), но мы предоставляем значение по умолчанию для T
на основе R
. В этом конкретном примере T
по умолчанию будет P
. И теперь fill(array, {})
будет работать по той же причине, что и P p = {}
.
Действительно, P2248 вносит именно это изменение в fill
. Он был принят на конференции C++26 в Токио в 2024 году.