Judging by how the authors wrote the final implementation of is_detected , they assumed that Op is a variation pattern that allows many more concepts to be expressed:
(Also pulled from n4502 )
When you enter such a scenario, it becomes necessary void for the specialization of the template to match the true_type version when Op<Args...> is a valid expression.
Here, my setup for the original detection will be volatile :
template<class...> using void_t = void;
And then let it define a sign for checking the Foo function, which may or may not accept certain types:
template<class T, class... Args> using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...)); template< class T, class... Args> using has_foo = detect<T, HasFoo_t, void, Args...>;
And the structure for testing:
struct A { void Foo(int) { std::cout << "A::Foo(int)\n"; } };
And finally, test (s):
std::cout << std::boolalpha << has_foo<A, int>::value << std::endl; //true std::cout << std::boolalpha << has_foo<A>::value << std::endl; // false
If I remove void from my has_foo , then the main specialization is selected, and I get false ( Example ).
This is because void exists so that it matches void_t<Op<T, Args...>> , which, if you recall, will be of type void if the template argument is well-formed. If the template argument is not correct, then void_t<Op<T, Args...>> not a good match and it will return to the default specialization ( false_type ). When we remove void from our call (and just leave Args... in its place), we cannot match the argument void_t<Op<T, Args...>> in the true_type specialization.
So, the authors wanted to remove this void , which will fall into the client code, which means a slightly more complicated implementation later in the document.
Thanks to @Rerito for pointing this answer , where Yakk also adds a bit of extra work to avoid pesky void in client code.