piyo
piyo

Reputation: 471

C++ noexcept declaration changes template deduction

I was tinkering to confirm the example on page 91 of Effective Modern C++, and I ran into what seems to be a strange issue. This code

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);
}

Gives me an error like

error: request for member ‘front’ in ‘a’, which is of non-class type ‘int’ void doStuff(C& a, C& b) noexcept(noexcept(doStuff(a.front(), b.front()))) {

So, it seems like the top version of doStuff is being called, even though a.front() and b.front() should be returning references to ints. If I remove all the noexcept declarations from the code, I get the expected output.

This is with gcc 5.4.

What am I doing wrong?

Thanks

Upvotes: 8

Views: 846

Answers (2)

Barry
Barry

Reputation: 303387

The problem is, when the name lookup at this point:

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

will just find one doStuff(): your function template. The specialization hasn't been declared yet, so it isn't considered.

First thing to do is to simply avoid specializations. They're awkward. But then the real fix would be to stick in an extra empty type solely for argument-dependent lookup purposes. This will add a dependent name to the noexcept lookup that will delay invocation until instantiation:

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);
}

Upvotes: 7

John
John

Reputation: 7339

Template specializations are not overloads. Your specialization for doStuff<int> is not an overload of doStuff<C>, it is a specialization. So overload resolution doesn't consider it, template instantiation will consider it, if the original is selected by overload resolution. Replace your specialization with an overload (non-template, taking two int&s)

void doStuff(int& a, int& b) noexcept;

Upvotes: 2

Related Questions