Оператор == не компилируется, если я включаю <iostream> - PullRequest
5 голосов
/ 25 марта 2019

Следующий код прекрасно компилируется, если:

  1. Я не включаю <iostream> или

  2. Я называю operator== как alp::operator==.

Полагаю, есть проблема с <iostream> и operator==, но я не знаю, что.

Я компилирую код с помощью gcc 7.3.0, clang ++ - 6.0 и goldbolt. Всегда одна и та же ошибка.

Проблема в том, что компилятор пытается привести параметры operator== к const_iterator, но почему? (Я полагаю, компилятор не видит мою версию operator== и ищет другие версии).

#include <vector>
#include <iostream> // comment and compile


namespace alp{

template <typename It_base>
struct Iterator {
    using const_iterator    = Iterator<typename It_base::const_iterator>;

    operator const_iterator() { return const_iterator{}; }
};


template <typename It_base>
bool operator==(const Iterator<It_base>& x, const Iterator<It_base>& y)
{ return true;}

}// namespace

struct Func{
    int& operator()(int& p) const {return p;}
};


template <typename It, typename View>
struct View_iterator_base{
    using return_type     = decltype(View{}(*It{})); 

    using const_iterator =
              View_iterator_base<std::vector<int>::const_iterator, Func>;
};


using view_it =
    alp::Iterator<View_iterator_base<std::vector<int>::iterator, Func>>;


int main()
{
    view_it p{};
    view_it z{};

    bool x = operator==(z, p); // only compiles if you remove <iostream>
    bool y = alp::operator==(z,p); // always compile
}

Сообщение об ошибке:

yy.cpp: In instantiation of ‘struct View_iterator_base<__gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func>’:

yy.cpp:9:73:   required from ‘struct    alp::Iterator<View_iterator_base<__gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func> >’

yy.cpp:44:29:   required from here

yy.cpp:28:42: error: no match for call to ‘(Func) (const int&)’
 using return_type   = decltype(View{}(*It{}));
                                ~~~~~~^~~~~~~
yy.cpp:22:10: note: candidate: int& Func::operator()(int&) const <near match>
 int& operator()(int& p) const {return p;}
      ^~~~~~~~
yy.cpp:22:10: note:   conversion of argument 1 would be ill-formed:
yy.cpp:28:42: error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers
 using return_type   = decltype(View{}(*It{}));
                                ~~~~~~^~~~~~~

1 Ответ

5 голосов
/ 25 марта 2019

Я сделал более минимальный тестовый пример: https://godbolt.org/z/QQonMG.

Соответствующие данные:

  • A using псевдоним типа не создает шаблон.Например,

    template<bool b>
    struct fail_if_true {
        static_assert(!b, "template parameter must be false");
    };
    
    using fail_if_used = fail_if_true<true>;
    

    не приведет к ошибке времени компиляции (если fail_if_used не используется)

  • ADL также проверяет классы параметров шаблона.В этом случае std::vector<int>::iterator - это __gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func>, в шаблоне которого есть std::vector<int>.Так, operator== проверит в глобальном пространстве имен (всегда), alp (поскольку alp::Iterator в alp), __gnu_cxx и std.

  • Ваш View_iterator_base::const_iterator является недействительным.View_iterator_base::const_interator::result_type определяется как decltype(Func{}(*std::vector<int>::const_iterator{})).std::vector<int>::const_iterator{} будет итератором констант векторов, поэтому *std::vector<int>::const_iterator{} будет const int&.Func::operator() занимает int&, так что это означает, что выражение недопустимо.Но это не приведет к ошибке времени компиляции, если не используется по причинам, указанным выше.Это означает, что ваш оператор преобразования относится к недопустимому типу.
  • Поскольку вы не определите его как explicit, будет использован оператор преобразования (в недопустимый тип), чтобы попытаться сопоставить его с функциейпараметры, если они еще не совпадают.Очевидно, это в конечном итоге создаст экземпляр недопустимого типа, поэтому он выдаст ошибку времени компиляции.
  • Я предполагаю, что iostream включает string, что определяет std::operator== для строк.

Вот пример без пространства имен std: https://godbolt.org/z/-wlAmv

// Avoid including headers for testing without std::
template<class T> struct is_const { static constexpr const bool value = false; } template<class T> struct is_const<const T> { static constexpr const bool value = true; }

namespace with_another_equals {
    struct T {};

    bool operator==(const T&, const T&) {
        return true;
    }
}

namespace ns {
    template<class T>
    struct wrapper {
        using invalid_wrapper = wrapper<typename T::invalid>;
        operator invalid_wrapper() {}
    };

    template<class T>
    bool operator==(const wrapper<T>&, const wrapper<T>&) {
        return true;
    }
}

template<class T>
struct with_invalid {
    static_assert(!is_const<T>::value, "Invalid if const");
    using invalid = with_invalid<const T>;
};

template<class T>
void test() {
    using wrapped = ns::wrapper<with_invalid<T>>;
    wrapped a;
    wrapped b;
    bool x = operator==(a, b);
    bool y = ns::operator==(a, b);
}

template void test<int*>();

// Will compile if this line is commented out
template void test<with_another_equals::T>();

Обратите внимание, что просто объявление operator const_iterator() должно создать экземпляр типа.Но это не так, потому что это в шаблонах.Я предполагаю, что он оптимизирован (где он выполняет компиляцию, потому что он не используется), прежде чем его можно будет проверить, чтобы показать, что он не может скомпилироваться (он даже не предупреждает с -Wall -pedantic, что у него нет оператора возвратав моем примере).

...