Почему const char[] лучше подходит для std::range :: range, чем для явной перегрузки, свободной от const char*, и как это исправить?
Я хотел написать общий <<
для любого range
и я закончил с этим:
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range) {
using namespace std::ranges;
if (empty(range)) {
return out << "[]";
}
auto current = begin(range);
out << '[' << *current;
while(++current != end(range)) {
out << ',' << *current;
}
return out << ']';
}
Проверено так:
int main() {
std::vector<int> ints = {1, 2, 3, 4};
std::cout << ints << '\n';
}
он отлично работает и выводит:
[1,2,3,4]
Но при тестировании с:
int main() {
std::vector<int> empty = {};
std::cout << empty << '\n';
}
он неожиданно выводит:
[[,], ]
Запустив этот код с отладчиком, я пришел к выводу, что проблема с пустым диапазоном заключается в том, что мы запускаем return out << "[]";
. Некоторая магия C++ решила, что моя, только что написанная,
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
это лучше подходит то, при условии, в<ostream>
,
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
поэтому вместо того, чтобы просто отправлять "[]"
в выходной поток, как мы привыкли видеть, он возвращается к самому себе, но с "[]"
как range
аргумент.
В чем причина того, что это лучший матч? Могу ли я исправить это более элегантно по сравнению с отправкой[
а также ]
раздельно?
РЕДАКТИРОВАТЬ: Похоже, что это, скорее всего, ошибка в GCC 10.1.0, поскольку более новые версии отклоняют код.
1 ответ
Я думаю, это не должно компилироваться. Давайте немного упростим пример:
template <typename T> struct basic_thing { };
using concrete_thing = basic_thing<char>;
template <typename T> concept C = true;
void f(concrete_thing, C auto&&); // #1
template <typename T> void f(basic_thing<T>, char const*); // #2
int main() {
f(concrete_thing{}, "");
}
В basic_thing
/concrete_thing
имитирует то, что происходит с basic_ostream
/ostream
. #1
это перегрузка, которую вы предоставляете, #2
находится в стандартной библиотеке.
Очевидно, обе эти перегрузки жизнеспособны для вызова, который мы делаем. Какой из них лучше?
Что ж, они оба точные совпадения в обоих аргументах (да, char const*
точно соответствует ""
даже несмотря на то, что у нас происходит распад указателя, см. Почему распад указателя имеет приоритет над выведенным шаблоном?). Таким образом, последовательности преобразования не могут различаться.
Оба они являются шаблонами функций, поэтому их нельзя различить.
Ни один из шаблонов функций не является более специализированным, чем другой - вывод не работает в обоих направлениях (char const*
не может соответствовать C auto&&
а также concrete_thing
не может соответствовать basic_thing<T>
).
"Более ограниченная" часть применяется только в том случае, если настройка параметров шаблона одинакова в обоих случаях, что здесь неверно, поэтому эта часть не имеет значения.
И... вот и все, тай-брейк закончился. Тот факт, что gcc 10.1 принял эту программу, был ошибкой, gcc 10.2 больше не принимает. Хотя сейчас работает clang, и я считаю, что это ошибка clang. MSVC отвергает как неоднозначное: Demo.
В любом случае, здесь есть простое решение - написать [
а потом ]
как отдельные символы.
И в любом случае вы, вероятно, не захотите писать
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
для начала, поскольку для того, чтобы это действительно работало правильно, вам нужно будет вставить его в пространство имен std
. Вместо этого вы хотите написать оболочку для произвольного диапазона и использовать ее:
template <input_range V> requires view<V>
struct print_view : view_interface<print_view<V>> {
print_view() = default;
print_view(V v) : v(v) { }
auto begin() const { return std::ranges::begin(v); }
auto end() const { return std::ranges::end(v); }
V v;
};
template <range R>
print_view(R&& r) -> print_view<all_t<R>>;
И определите свой operator<<
напечатать print_view
. Таким образом, это просто работает, и вам не нужно заниматься этими проблемами. Демо.
Конечно, вместо out << *current;
вы можете условно обернуть это в out << print_view{*current};
чтобы быть полностью правильным, но я оставлю это как упражнение.