Как ADL должен работать для этого?


Недавно я столкнулся с проблемой с компилятором clang++ 5.0.0, когда через ADL он не подбирал правильную функцию на Mac (но g ++ делал это правильно на Linux). Я хотел бы знать, является ли это компилятором ИЛИ плохой дизайн класса вообще.
Вот пример кода (исключительно для иллюстрации):

namespace test {
    class Ops {
      public:
        Ops():val_(0){}
        template<typename T>
        Ops& operator<< (const T& val) {
            std::cout << "Called member function" << std::endl;
            this->val_ = val;
            return *this;
        }
      private:
        int val_;
    };

    template<typename T>
    struct Any {
        T val_;
    };

    template <template<typename> class E,  typename T>
    Ops& operator<< (Ops& op, const E<T>& val) {
        std::cout << "Global function" << std::endl;
        return op;
    }
}

int main() {
    test::Ops op;
    int k = 9;
    test::Any<int> a;
    op << a;

    return 0;
}

Я хотел бы знать, как ADL и вывод аргументов шаблона будут работать поэтапно, чтобы найти лучшее соответствие?
Будет ли какая-либо ситуация для того же "основного тела", в котором функция-член предпочтительнее, чем свободная функция? (Это то, что происходит в сборке продукта)

Заранее спасибо.

2 ответа

Решение

Это то, что происходит в деталях и что должен делать каждый компилятор: кандидатная функция шаблона найдена квалифицированным поиском

template <typename T>
test::Ops::operator<<(const T&)

в то время как второй кандидат генерируется через ADL с использованием вывода аргументов шаблона (ср. temp.deduct.conv)

template <template <typename> class E, typename T>
test::operator<<(test::Ops&, const E<T>&)

После этого срабатывает разрешение перегрузки (ср. 13.3.3), и не член (F1) предпочтительнее, чем член (F2), так как

  • F1 и F2 являются специализациями шаблонов функций, а шаблон функций для F1 более специализирован, чем шаблон для F2 в соответствии с правилами частичного упорядочения, описанными в 14.5.6.2.

и, таким образом, выбран в качестве функции для вызова.

Чтобы ответить на ваш вопрос: это зависит от правил разрешения перегрузки. Будучи функцией-членом или во внутренней области видимости не влияет на результат и что-то вроде

namespace test {
    class Ops {
      public:
        Ops():val_(0){}

        template<typename T>
        Ops& operator<< (const T& val) {
            std::cout << "Called member function" << std::endl;
            this->val_ = val;
            return *this;
        }

      private:
        int val_;
    };

    template<typename T>
    struct Any {
        T val_;
    };

    template <typename E>
    Ops& operator<< (Ops& op, const E& val) {
        std::cout << "Global function" << std::endl;
        return op;
    }
}

просто вызовет ошибку разрешения перегрузкиuse of overloaded operator '<<' is ambiguous".

Как плюс: функция-член неверна, даже если она была выбрана: this->val присваивается нецелочисленный тип.

Эти две функции-кандидата находятся в наборе перегрузки:

// member function template, found by qualified lookup
template <typename T>
test::Ops::operator<<(const T&)

// non-member function template, found by ADL
template <template <typename> class E, typename T>
test::operator<<(test::Ops&, const E<T>&)

При поиске операторов предпочтение не предоставляется членам, а не членам. После подстановки аргумента шаблона обе специализации шаблона функции точно соответствуют (с квалификационными преобразованиями) предоставленным типам аргументов. Но функция, принимающая E<T> более специализирован, чем принимающий T, поэтому функция, не являющаяся членом, выбрана по этой причине.

Apple Clang 5.0.0 основан на LLVM clang 3.3svn. Я не могу найти ни одной версии LLVM Clang, которая выбирает функцию-член. Это может быть ошибкой в ​​коде Apple, но, скорее всего, это будет небольшая разница в коде, который вы на самом деле компилируете, или в вашей среде. Вы пытались скомпилировать пример кода с подозрительным компилятором?

Другие вопросы по тегам