Use of void template argument in early detection idiom implementation

前端 未结 1 582
心在旅途
心在旅途 2020-12-19 06:37

In n4502 the authors describe an early implementation of the detect idiom that encapsulates the void_t trick. Here\'s its definition along with usage for defini

相关标签:
1条回答
  • 2020-12-19 07:04

    Judging on how the authors wrote their final implementation of is_detected, they intended that Op be a variadic template, which allows one to express many more concepts:

    (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 get into a scenario like this, a void becomes necessary so that template specialization will match the true_type version when Op<Args...> is a valid expression.

    Here's my tweak on the original detect to be variadic:

    // primary template handles all types not supporting the operation:
    template< class T, template<class...> class Trait, class... TraitArgs >
    struct
    detect : std::false_type { };
    // specialization recognizes/validates only types supporting the archetype:
    template< class T, template<class...> class Trait, class... TraitArgs >
    struct
    detect< T, Trait, std::void_t<Trait<T, TraitArgs...>>, TraitArgs... > : std::true_type { };
    
    template<class T, template<class...> class Trait, class... TraitArgs>
    using is_detected_t = typename detect<T, Trait, void, TraitArgs...>::type; 
    
    template<class T, template<class...> class Trait, class... TraitArgs>
    constexpr bool is_detected_v = detect<T, Trait, void, TraitArgs...>::value;
    

    Note that I renamed Op to Trait, Args to TraitArgs, and used std::void_t which made it into C++17.

    Now let's define a trait to test for a function named Foo that can may or may not accept certain parameter types:

    template<class T, class... Args>
    using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...));
    

    Now we can get a type (true_type or false_type) given some T and our trait:

    template< class T, class... Args>
    using has_foo_t = is_detected_t<T, HasFoo_t, Args...>;
    

    And finally, we can also "just check" to see if the trait is valid for some provided T and Args:

    template<class T, class... Args>
    constexpr bool has_foo_v = is_detected_v<T, HasFoo_t, Args...>;
    

    Here's a struct to start testing:

    struct A
    {
        void Foo(int)
        {
            std::cout << "A::Foo(int)\n";
        }
    };
    

    And finally the test(s):

    std::cout << std::boolalpha << has_foo_v<A, int> << std::endl; //true
    std::cout << std::boolalpha << has_foo_v<A> << std::endl; // false
    

    If I remove the void from my is_detected_t and is_detected_v implementations, then the primary specialization is chosen, and I get false (Example).

    This is because the void is there so as to match std::void_t<Trait<T, TraitArgs...>> which if you recall will have a type of void if the template argument is well-formed. If the template argument is not well-formed, then std::void_t<Trait<T, TraitArgs...>> is not a good match and it will revert to the default specialization (false_type).

    When we remove void from our call (and simply leave TraitArgs... in its place) then we cannot match the std::void_t<Trait<T, TraitArgs...>> argument in the true_type specialization.

    Also note that if std::void_t<Trait<T, TraitArgs...>> is well-formed, it simply provides a void type to the class... TraitArgs argument in the primary template, so we don't need to define an extra template parameter to receive void.

    In conclusion, the authors wanted to remove the void that would end up in client code, hence their slightly more complicated implementation later in the paper.

    Thanks to @Rerito for pointing out this answer where Yakk also puts in a little extra work to avoid the pesky void in client code.

    0 讨论(0)
提交回复
热议问题