Using the void template argument to implement an early detection idiom

In n4502, authors describe an early implementation of the discovery idiom that encapsulates the void_t trick. Here is its definition along with the use to determine the characteristic for is_assignable (indeed it is is_copy_assignable )

 template<class...> using void_t = void; // primary template handles all types not supporting the operation: template< class, template<class> class, class = void_t< > > struct detect : std::false_type { }; // specialization recognizes/validates only types supporting the archetype: template< class T, template<class> class Op > struct detect< T, Op, void_t<Op<T>> > : std::true_type { }; // archetypal expression for assignment operation: template< class T > using assign_t = decltype( std::declval<T&>() = std::declval<T const &>() ); // trait corresponding to that archetype: template< class T > using is_assignable = detect<void, assign_t, T>; 

They mention that they do not like it because of the void used in the is_assignable :

Although the resulting code was much more understandable than the original, we did not like the aforementioned discovery interface, because the void argument in the metafunction call is an implementation detail that should not leak into the client code.

However, void does not make any sense to me in the first place. If I try to use this type to determine if int is an assignable copy, I get a std::false_type demo .

If I rewrite is_assignable as:

 template< class T > using is_assignable = detect<T, assign_t>; 

Which makes more sense to me, then the symptom works correctly: Demo

So my question here is I misunderstanding something in this document or is it just a typo?

If it was a typo, then I don’t understand why the authors felt the need to discuss how they do not like the void leak, which makes me sure that I just missed something.

+7
c ++ language-lawyer template-meta-programming c ++ 17
source share
1 answer

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 )

 // primary template handles all types not supporting the archetypal Op: template< class Default , class // always void; supplied externally , template<class...> class Op , class... Args > struct detector { using value_t = false_type; using type = Default; }; // the specialization recognizes and handles only types supporting Op: template< class Default , template<class...> class Op , class... Args > struct detector<Default, void_t<Op<Args...>>, Op, Args...> { using value_t = true_type; using type = Op<Args...>; }; //... template< template<class...> class Op, class... Args > using is_detected = typename detector<void, void, Op, Args...>::value_t; 

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; // primary template handles all types not supporting the operation: template< class, template<class...> class, class... > struct detect : std::false_type { }; // specialization recognizes/validates only types supporting the archetype: template< class T, template<class...> class Op, class... Args > struct detect< T, Op, void_t<Op<T, Args...>>, Args... > : std::true_type { }; 

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.

+1
source share

All Articles