Сравнение для объектов, полученных из std :: string_view, неоднозначно в MSVC

TL; ДР: Могу ли я ожидать, что приведенный ниже код будет скомпилирован на любом совместимом с ++ 17 наборе инструментов с ++ (на основе текущего предложения на с ++ 17), и отказ MSVC сделать это является ошибкой в ​​их реализации?

#include <string_view>

struct Foo : std::string_view {};

int main() {
  Foo f1{};
  Foo f2{};
  return f1 == f2;
}

Объяснение:
У меня есть класс, который получен из std::string_view и не реализует свои собственные операторы сравнения, потому что std::string_view семантика - это именно то, что мне нужно, и я также хочу, чтобы она была сравнима с, например, std::string.

Однако, если я попытаюсь сравнить два экземпляра этого класса, MSVC 2017 жалуется на множественные перегрузки с похожими преобразованиями:

example.cpp
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xlocale(314): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
8 : <source>(8): error C2666: 'std::operator ==': 3 overloads have similar conversions
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/exception(336): note: could be 'bool std::operator ==(const std::exception_ptr &,const std::exception_ptr &) throw()' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/exception(341): note: or       'bool std::operator ==(std::nullptr_t,const std::exception_ptr &) throw()' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/exception(346): note: or       'bool std::operator ==(const std::exception_ptr &,std::nullptr_t) throw()' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(362): note: or       'bool std::operator ==(const std::error_code &,const std::error_code &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(370): note: or       'bool std::operator ==(const std::error_code &,const std::error_condition &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(378): note: or       'bool std::operator ==(const std::error_condition &,const std::error_code &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(386): note: or       'bool std::operator ==(const std::error_condition &,const std::error_condition &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xstring(970): note: or       'bool std::operator ==<char,std::char_traits<char>>(const std::basic_string_view<char,std::char_traits<char>>,const std::basic_string_view<char,std::char_traits<char>>) noexcept'
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xstring(980): note: or       'bool std::operator ==<char,std::char_traits<char>,Foo&,void>(_Conv,const std::basic_string_view<char,std::char_traits<char>>) noexcept(<expr>)'
        with
        [
            _Conv=Foo &
        ]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xstring(990): note: or       'bool std::operator ==<char,std::char_traits<char>,Foo&,void>(const std::basic_string_view<char,std::char_traits<char>>,_Conv) noexcept(<expr>)'
        with
        [
            _Conv=Foo &
        ]
8 : <source>(8): note: while trying to match the argument list '(Foo, Foo)'
Microsoft (R) C/C++ Optimizing Compiler Version 19.10.25017 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.
Compiler exited with result code 2

Я не знаю, почему первые несколько перегрузок (например, с std::error_code) перечислены вообще. Так как само сообщение об ошибке говорит только о 3 перегрузках, я предполагаю, что они существуют только для полноты, но не являются частью проблемы.

Что меня смущает, так это две эти перегрузки:

bool std::operator ==<char,std::char_traits<char>,Foo&,void>(_Conv,const std::basic_string_view<char,std::char_traits<char>>) noexcept(<expr>)
bool std::operator ==<char,std::char_traits<char>,Foo&,void>(const std::basic_string_view<char,std::char_traits<char>>,_Conv) noexcept(<expr>)

Я не смог найти упоминаний о них в cppreference.com, и код прекрасно компилируется в clang и gcc: https://godbolt.org/g/4Lj5qv , поэтому они, вероятно, отсутствуют в их реализации.

Так что мой вопрос

  • Действительно ли их существование разрешено (или даже предписано) ожидаемым стандартом c ++ 17, или это ошибка в MSVC?
  • Если что-то подобное разрешено в стандартной соответствующей стандартной библиотеке c ++, есть ли простой обходной путь, который не требует от меня реализации всех компараторов самостоятельно (я знаю, что писать их тривиально, но это не должно быть необходимо, и мне придется повторить процесс для нескольких типов).

ИЗМЕНИТЬ :
Просто для справки, фактический Foo является неизменным строковым классом, очень похожим на этот: https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string , но для упрощения дизайна Я хотел заменить свой свернутый вручную str_ref на std::string_view

7 голосов | спросил MikeMB 18 AM000000110000005931 2017, 11:23:59

1 ответ


0

Да, вы должны ожидать, что ваш код будет работать; Вывод аргументов шаблона может определить базовый класс в вызовах функций, см. [temp.deduct .call] /4.3

  

- Если P является классом и P имеет форму simple-template-id , тогда преобразованный A может быть производным классом выведенный A.

Проблема с VS 2017 (15.3) заключается в том, что в стандарте также предусмотрены ситуации, когда один из аргументов неявно преобразуется в std::string_view, см. [ string.view.comparison] :

  

Пусть S будет basic_­string_­view<charT, traits>, и sv быть   экземпляр S. Реализации должны обеспечивать достаточные дополнительные   перегрузки отмечены constexpr и noexcept, так что объект t с   неявное преобразование в S можно сравнить в соответствии с таблицей 67.

     

Таблица 67. Дополнительные basic_­string_­view сравнительные перегрузки

     
  • Выражение t == sv эквивалентно: S(t) == sv
  •   
  • Выражение sv == t эквивалентно: sv == S(t)
  •   
  • . , .
  •   

[Пример: пример реализации, соответствующей operator==:

template<class T> using __identity = decay_t<T>;
template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT, traits> lhs,
                            basic_string_view<charT, traits> rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }
template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT, traits> lhs,
                            __identity<basic_string_view<charT, traits>> rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }
template<class charT, class traits>
  constexpr bool operator==(__identity<basic_string_view<charT, traits>> lhs,
                            basic_string_view<charT, traits> rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }
     

- конец примера]

Это вызывает проблему в VS 2017 (15.3), потому что:

  • Компилятор MSVC не может обработать частичное упорядочение шаблонов функций относительно не выводимый контекст (спасибо @ T.C.), поэтому реализация, упомянутая в стандарте, невозможна

  • Следовательно, стандартная библиотека MSVC работает с SFINAE для перегрузок № 2 и № 3, см. xstring:

template<class _Elem,
  class _Traits,
  class _Conv, // TRANSITION, VSO#265216
  class = enable_if_t<is_convertible<_Conv, basic_string_view<_Elem, _Traits>>::value>>
  _CONSTEXPR14 bool operator==(_Conv&& _Lhs, const basic_string_view<_Elem, _Traits> _Rhs)
      _NOEXCEPT_OP(_NOEXCEPT_OP((basic_string_view<_Elem, _Traits>(_STD forward<_Conv>(_Lhs)))))
  {   // compare objects convertible to basic_string_view instances for equality
  return (_Rhs._Equal(_STD forward<_Conv>(_Lhs)));
  }

template<class _Elem,
  class _Traits,
  class _Conv, // TRANSITION, VSO#265216
  class = enable_if_t<is_convertible<_Conv, basic_string_view<_Elem, _Traits>>::value>>
  _CONSTEXPR14 bool operator==(const basic_string_view<_Elem, _Traits> _Lhs, _Conv&& _Rhs)
      _NOEXCEPT_OP(_NOEXCEPT_OP((basic_string_view<_Elem, _Traits>(_STD forward<_Conv>(_Rhs)))))
  {   // compare objects convertible to basic_string_view instances for equality
  return (_Lhs._Equal(_STD forward<_Conv>(_Rhs)));
  }

К сожалению, это не то же самое, что подразумевалось в стандарте - поскольку сигнатура этих перегрузок отличается от оригинальной, и Foo&& лучше соответствует, чем std::string_view (еще раз спасибо @TC), частичное упорядочение между # 1 отсутствует. Выполняются # 2 и # 3 - разрешение перегрузки выбирает # 2 и # 3 в качестве лучших кандидатов. Теперь эти двое действительно неоднозначны - оба жизнеспособны, но ни один не является более специализированным.

В качестве обходного пути вы можете реализовать компараторы для ваших типов или просто общий компаратор для случаев, когда обе стороны конвертируются в string_view:

#include <string_view>

template<class T, class T2,
  class = std::enable_if_t<std::is_convertible<T, std::string_view>::value>,
  class = std::enable_if_t<std::is_convertible<T2, std::string_view>::value>>
constexpr bool operator==(T&& lhs, T2&& rhs) noexcept
{
  return lhs.compare(std::forward<T2>(rhs));
}

struct Foo : std::string_view {};

int main() {
  Foo f1{};
  Foo f2{};
  return f1 == f2;
}
ответил rustyx 18 PM000000120000000731 2017, 12:31:07

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132