Объявление C ++ noexcept изменяет вывод шаблона

Я пытался подтвердить пример на стр. 91 книги «Эффективный современный C ++» и натолкнулся на странную проблему. Этот код

template<typename C>
void doStuff(C& a, C& b) noexcept(noexcept(doStuff(a.front(), b.front()))) {
    std::cout << "container version" << std::endl;
}

template<>
void doStuff<int>(int& x, int& y) noexcept {
    std::cout << "int version" << std::endl;
}

int main() {
    vector<int> v1 = {1, 2, 3};
    vector<int> v2 = {4, 5, 6};
    int x = 5;
    int y = 6;
    doStuff(x, y);
    doStuff(v1, v2);
}

Выдает ошибку вроде

ошибка: запрос члена 'front' в 'a', который имеет неклассовый тип 'int' void doStuff (C & a, C & b) noexcept (noexcept (doStuff (a.front (), b.front ()) )) {

Итак, похоже, что вызывается верхняя версия doStuff, хотя a.front () и b.front () должны возвращать ссылки на ints. Если я удалю из кода все объявления noexcept, я получу ожидаемый результат.

Это с gcc 5.4.

Что я делаю неправильно?

Спасибо


person piyo    schedule 18.08.2016    source источник
comment
Помните, что шаблоны - это функция compile.time. Когда определена первая версия функции doStuff, компилятор ничего не знает о специализации для int.   -  person Some programmer dude    schedule 18.08.2016
comment
Также существует проблема, заключающаяся в том, что doStuff не был объявлен при первом использовании в спецификации noexcept.   -  person aschepler    schedule 18.08.2016


Ответы (2)


Проблема в том, что поиск имени на этом этапе:

template<typename C>
void doStuff(C& a, C& b) noexcept(noexcept(doStuff(a.front(), b.front()))) {
//                                         ^^^^^^^

найдет только один doStuff(): шаблон вашей функции. Специализация еще не заявлена, поэтому не рассматривается.

Первое, что нужно сделать, это просто избегать специализаций. Они неловкие. Но тогда реальным решением было бы придерживаться лишнего пустого типа исключительно для целей поиска, зависящего от аргумента. Это добавит зависимое имя в поиск noexcept, которое задержит вызов до создания экземпляра:

namespace N {
    struct adl { };

    void doStuff(adl, int& , int& ) noexcept {
        std::cout << "int version" << std::endl;
    }

    template<typename C>
    void doStuff(adl, C& a, C& b) noexcept(noexcept(doStuff(adl{}, a.front(), b.front()))) {
        std::cout << "container version" << std::endl;
    }
}

template <class C>
void doStuff(C& a, C& b) noexcept(noexcept(doStuff(N::adl{}, a, b)))
{
    doStuff(N::adl{}, a, b);
}
person Barry    schedule 18.08.2016
comment
Неужели перегрузка int должна быть первой? Разве doStuff в спецификаторе noexcept не является зависимым символом, что означает, что он не разрешается до создания экземпляра? - person John; 18.08.2016
comment
Удивительно, но это не удастся для C=std::vector<std::vector<int>>. - person aschepler; 18.08.2016
comment
Как это работает? adl не зависимое имя, не так ли? - person aschepler; 18.08.2016
comment
OK. Это работает. Я, естественно, предположил, что это как-то связано с еще не объявленной специализацией, но я знал, что должен быть правильный способ обработки таких сценариев, учитывая, что я подражал чему-то в стандартной библиотеке. - person piyo; 18.08.2016
comment
Хм. Это работает, но я не знаю, как это сделать. Я предполагал, что использование пространства имен хотя бы частично отвечает за возникновение необходимой задержки, но оно по-прежнему работает без дополнительного пространства имен. - person piyo; 18.08.2016

Специализации шаблонов не являются перегрузками. Ваша специализация для doStuff<int> - это не перегрузка doStuff<C>, это специализация. Таким образом, разрешение перегрузки не учитывает это, создание экземпляра шаблона будет учитывать это, если оригинал выбран с помощью разрешения перегрузки. Замените свою специализацию перегрузкой (не шаблон, два int&s)

void doStuff(int& a, int& b) noexcept;
person John    schedule 18.08.2016