Search code examples
c++templatesvisual-c++compiler-errorssfinae

noexcept + declval fail to compile under MSVC


I'm trying to implement the answer I got to my SO question here: My goal is to detect the existence of void cancel() noexcept method in a template class T. here is my minimal example:

#include <iostream>
#include <type_traits>

template<class T, class = void>
struct has_cancel : std::false_type {};

template<class T>
struct has_cancel<T,
    typename std::void_t<decltype(std::declval<T>().cancel()),
    typename std::enable_if_t<noexcept(std::declval<T>().cancel())>
    >
> : std::true_type {};

void print_has_cancel(std::true_type) {
    std::cout << "cancel found" << std::endl;
}

void print_has_cancel(std::false_type) {
    std::cout << "cancel not found" << std::endl;
}

struct A{

};

struct B {
    void cancel(){}
};

struct C {
    int cancel() noexcept {}
};

struct D{
    void cancel() noexcept {}
};

int main(){
    print_has_cancel(has_cancel<A>());
    print_has_cancel(has_cancel<B>());
    print_has_cancel(has_cancel<C>());
    print_has_cancel(has_cancel<D>());
    return 0;
}

output from Visual studio community 2019:

1>------ Build started: Project: cpp_sandbox, Configuration: Debug x64 ------
1>cpp_sandbox.cpp
1>C:\Users\David Haim\source\repos\cpp_sandbox\cpp_sandbox\cpp_sandbox.cpp(10,1): error C2228:  left of '.cancel' must have class/struct/union
1>C:\Users\David Haim\source\repos\cpp_sandbox\cpp_sandbox\cpp_sandbox.cpp(10,1): message :  type is '_Add_reference<_Ty,void>::_Rvalue'
1>C:\Users\David Haim\source\repos\cpp_sandbox\cpp_sandbox\cpp_sandbox.cpp(10,19): error C2056:  illegal expression
1>Done building project "cpp_sandbox.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

After some research, the problem is with the grammar noexcept(std::declval<type>().something()) - MSVC refuses to understand this line.

1) why is MSVC complaining? is the bug in the code or in the compiler?

2) how can I still make it compile?


Solution

  • MSVC doesn't have a problem with the syntax itself. It just seems to assume that SFINAE does not apply in this case, so substituting void as type gives the error. Why or whether there is any support for this in the standard, I don't know at the moment (but I doubt it).

    This can be resolved easily by moving the noexcept condition from the declaration into the definition of the struct, so that it will never be substituted if the first decltype is ill-formed (i.e. if .cancel() doesn't exist at all) and SFINAE kicked in:

    template<class T>
    struct has_cancel<T,
        typename std::void_t<decltype(std::declval<T>().cancel())    >
    > : std::bool_constant<noexcept(std::declval<T>().cancel())> {};