MSVC refuses to compile this:
#include <type_traits>
struct G { void operator()() const { } };
template<class T>
using MyTrait = std::is_void<T>;
struct S
{
template<class F>
std::enable_if_t<MyTrait<decltype(std::declval<F>()())>::value> run(F &&);
};
template<>
void S::run(G &&);
with the error:
<source>(14): error C2910: 'S::run': cannot be explicitly specialized
<source>(16): error C2760: syntax error: 'int' was unexpected here; expected ';'
But Clang and GCC compile it just fine. And they all compile it fine if I say std::is_void
instead of MyTrait
.
Why is this? Is it a compiler bug, or is there something in the language that causes this problem?
This is a compiler bug. Defining explicit specializations of member function templates is perfectly legal.
std::enable_if_t<MyTrait<decltype(std::declval<F>()())>::value>
is ultimately an alias for void
with SFINAE, and the use of aliases shouldn't prevent specialization.
In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations, explicit specializations, and certain friend declarations.
The process to determine whether the full specialization void S::run(G&&)
matches the member function template is the same as if you had called S::run(g)
where g
is an argument of type G
.
During this process, std::enable_if_t<MyTrait<decltype(std::declval<F>()())>::value>
where F = G
would turn into void
.
It's worth noting that your example does compile with:
template<class F>
std::enable_if_t<MyTrait<decltype(G{}())>::value> run(F&&);
... which is pretty much the same thing, and MSVC is being inconsistent here.
MSVC 19.35 compiles this, but more recent versions raise an error. I was unable to find a bug report for this issue, perhaps because it is relatively recent and no one has noticed/reported it yet.
I've submitted a bug report for this issue.