Как я могу проверить, что присвоение const_reverse_iterator для reverse_iterator является недействительным? - PullRequest
0 голосов
/ 11 сентября 2018

Рассмотрим следующее:

using vector_type = std::vector<int>;
using const_iterator = typename vector_type::const_iterator;
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
using iterator = typename vector_type::iterator;
using reverse_iterator = typename vector_type::reverse_iterator;

int main()
{
    static_assert(!std::is_assignable_v<iterator, const_iterator>); // passes
    static_assert(!std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // fails
    static_assert(std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // passes
}

Я могу проверить, что присвоение iterator{} = const_iterator{} недопустимо, но не присвоение reverse_iterator{} = const_reverse_iterator{} с этим типом черты.

Это поведениесогласован для gcc 9.0.0 , clang 8.0.0 и MSVC 19.00.23506

Это прискорбно, поскольку реальностьчто reverse_iterator{} = const_reverse_iterator{} на самом деле не компилируется ни с одним из вышеупомянутых компиляторов.

Как можно надежно проверить, что такое присвоение недопустимо?

Такое поведение признака типа подразумевает, чтовыражение

std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>() 

правильно сформировано согласно [meta.unary.prop], и это похоже на мои собственные попытки использовать черту типа is_assignable.

Ответы [ 2 ]

0 голосов
/ 11 сентября 2018

Это просто вопрос ограничений.Или ее отсутствие.Вот сокращенный пример с другой чертой:

struct X {
    template <typename T>
    X(T v) : i(v) { }

    int i;
};

static_assert(is_constructible_v<X, std::string>); // passes
X x("hello"s); // fails

Всякий раз, когда люди говорят о том, чтобы быть SFINAE-дружественными - это в основном то, что они имеют в виду.Обеспечение того, что черты типа дают правильный ответ.Здесь X претендует на то, чтобы быть конструктивным из чего угодно - но на самом деле это не так.is_constructible на самом деле не создает экземпляр всей конструкции, он просто проверяет правильность выражения - это просто проверка на уровне поверхности.Это проблема, которую enable_if и более поздние Концепции призваны решить.

Для libstdc ++, в частности, у нас есть :

template<typename _Iter>
_GLIBCXX17_CONSTEXPR
reverse_iterator(const reverse_iterator<_Iter>& __x)
: current(__x.base()) { }

Здесь нет ограничений на _Iter, поэтому is_constructible_v<reverse_iterator<T>, reverse_iterator<U>> равно true для всех пар T, U, даже если это не на самом деле конструктивно.Вопрос использует assignable, но в этом случае присваивание будет проходить через этот шаблон конструктора, поэтому я говорю о конструкции.

Обратите внимание, что это, возможно, ошибка libstdc ++ и, возможно, недосмотр.Есть даже комментарий , что это должно быть ограничено:

/**
 *  A %reverse_iterator across other types can be copied if the
 *  underlying %iterator can be converted to the type of @c current.
 */
0 голосов
/ 11 сентября 2018

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

На самом деле его вызов не удался, потому что реализация метода содержит код, недопустимый для этих двухтипы.

В C ++ вы не можете проверить, приведет ли создание экземпляра метода к ошибке компиляции, вы можете проверить только эквивалент эквивалентного разрешения перегрузки, найдя решение.

Язык C ++ и стандартИзначально библиотека в значительной степени опиралась на «хорошо, тело метода компилируется только в шаблоне, если вызывается, поэтому, если тело неверно, программисту будет сказано».Более современный C ++ (как внутри, так и вне стандартной библиотеки) использует SFINAE и другие методы, чтобы сделать метод «не участвующим в разрешении перегрузки», когда его тело не будет компилироваться.

Конструктор обратного итератора из других обратных итераторовявляется старым стилем и не был обновлен до качества "не участвовать в разрешении перегрузки".

С n4713 , 27.5.1.3.1 [reverse.iter.cons] / 3:

  template<class U> constexpr reverse_iterator(const reverse_iterator<U>& u);

Эффекты: Инициализирует ток с помощью u.current.

Обратите внимание на отсутствие упоминания о «не участвует в разрешении перегрузки» или подобных словах.


Единственный способ получить желаемую черту - это

  1. Изменить (ну, исправить) стандарт C ++

  2. Особый случай это

Я оставлю 1. в качестве упражнения.Для 2. вы знаете, что обратные итераторы являются шаблонами перед прямыми итераторами.

template<class...Ts>
struct my_trait:std::is_assignable<Ts...> {};

template<class T0, class T1>
struct my_trait<std::reverse_iterator<T0>, std::reverse_iterator<T1>>:
  my_trait<T0, T1>
{};

и теперь my_trait равен is_assignable, за исключением обратных итераторов, где вместо этого проверяется назначаемость содержащихся итераторов.

(Как забавно, обратный обратный итератор будет работать с этой чертой).

Мне когда-то приходилось делать что-то очень похожее с std::vector<T>::operator<, который также слепо вызывал T<T и не делалSFINAE отключите его, если это недопустимо.


Может также случиться, что реализация стандартной библиотеки C ++ может создать конструктор, который не будет компилироваться, не будет участвовать в разрешении перегрузки.Это изменение может нарушить в противном случае правильно сформированные программы, но только из-за таких нелепых вещей, как ваше статическое утверждение (которое может измениться) или из-за логического эквивалентности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...