Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 208
piyo Avatar asked Aug 18 '16 17:08

piyo


People also ask

What is template argument deduction in c++?

Class Template Argument Deduction (CTAD) is a C++17 Core Language feature that reduces code verbosity. C++17's Standard Library also supports CTAD, so after upgrading your toolset, you can take advantage of this new feature when using STL types like std::pair and std::vector.

What is Noexcept false?

The noexcept specification is equivalent to the noexcept(true) specification. throw() is equivalent to noexcept(true) but was deprecated with C++11 and will be removed with C++20. In contrast, noexcept(false) means that the function may throw an exception.

How do I restrict a template type in C ++?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

What is Typename C++?

" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.


2 Answers

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);
}
like image 82
Barry Avatar answered Nov 16 '22 02:11

Barry


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;
like image 23
John Avatar answered Nov 16 '22 01:11

John