Search code examples
c++c++17sfinaedecltypevoid-t

Why does this custom type trait not compile


I'm not sure what I've done wrong below. I'm trying to write a trait can_visit and using the standard pattern of std::void_t and sfinae but it just won't compile and keeps telling me there is an error in the decltype statement containing the std::visit expression.

If I replace the line at #47 with

std::cout << can_visit<ABCVis,ABC>::value << std::endl;

it compiles but defeats the purpose.

std::cout << can_visit<ABVis,ABC>::value << std::endl;

should output 0 rather than failing to compile

#include <variant>
#include <iostream>

template <class TVisitor, class TVariant, class=void>
struct can_visit: std::false_type {};

template <class TVisitor, class TVariant >
struct can_visit
<
    TVisitor, 
    TVariant, 
    std::void_t<
        decltype(std::visit(
            std::declval<TVisitor&>(),
            std::declval<TVariant&>()
            )
        )
    >
>
 : std::true_type {};

struct A{};
struct B{};
struct C{};

using ABC = std::variant<A,B,C>;
using AB = std::variant<A,B>;

struct ABCVis {
    void operator()(A const & a){};
    void operator()(B const & a){};
    void operator()(C const & a){};
};
struct ABVis {
    void operator()(A const & a){};
    void operator()(B const & a){};
};


int main(){
    std::cout << can_visit<ABVis,ABC>::value << std::endl;
    
}

The full error message is here but can be executed on godbolt at https://godbolt.org/z/Gz4Ef4x1n

In file included from <source>:1:
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1020:11: error: no matching function for call to '__invoke'
          return std::__invoke(std::forward<_Visitor>(__visitor),
                 ^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1031:29: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &)>, std::integer_sequence<unsigned long, 2>>::__visit_invoke' requested here
      { return _Array_type{&__visit_invoke}; }
                            ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:976:48: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &)>, std::integer_sequence<unsigned long, 2>>::_S_apply' requested here
                std::index_sequence<__indices..., __index>>::_S_apply();
                                                             ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:955:7: note: in instantiation of function template specialization 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &), 3>, std::integer_sequence<unsigned long>>::_S_apply_single_alt<false, 2, std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &)>>' requested here
            (_S_apply_single_alt<false, __var_indices>(
             ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:940:2: note: in instantiation of function template specialization 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &), 3>, std::integer_sequence<unsigned long>>::_S_apply_all_alts<0, 1, 2>' requested here
        _S_apply_all_alts(
        ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1042:59: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &), 3>, std::integer_sequence<unsigned long>>::_S_apply' requested here
        = __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();
                                                                 ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1699:45: note: in instantiation of static data member 'std::__detail::__variant::__gen_vtable<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>::_S_vtable' requested here
        _Result_type, _Visitor&&, _Variants&&...>::_S_vtable;
                                                   ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1718:19: note: in instantiation of function template specialization 'std::__do_visit<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>' requested here
      return std::__do_visit<_Tag>(std::forward<_Visitor>(__visitor),
                  ^
<source>:13:23: note: in instantiation of function template specialization 'std::visit<ABVis &, std::variant<A, B, C> &>' requested here
        decltype(std::visit(
                      ^
<source>:47:18: note: during template argument deduction for class template partial specialization 'can_visit<TVisitor, TVariant>' [with TVisitor = ABVis, TVariant = std::variant<A, B, C>]
    std::cout << can_visit<ABVis,ABC>::value << std::endl;
                 ^
<source>:47:18: note: in instantiation of template class 'can_visit<ABVis, std::variant<A, B, C>>' requested here
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/bits/invoke.h:89:5: note: candidate template ignored: substitution failure [with _Callable = ABVis &, _Args = <C &>]: no type named 'type' in 'std::__invoke_result<ABVis &, C &>'
    __invoke(_Callable&& __fn, _Args&&... __args)
    ^
In file included from <source>:1:
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1041:36: error: constexpr variable '_S_vtable' must be initialized by a constant expression
      static constexpr _Array_type _S_vtable
                                   ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1699:45: note: in instantiation of static data member 'std::__detail::__variant::__gen_vtable<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>::_S_vtable' requested here
        _Result_type, _Visitor&&, _Variants&&...>::_S_vtable;
                                                   ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1718:19: note: in instantiation of function template specialization 'std::__do_visit<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>' requested here
      return std::__do_visit<_Tag>(std::forward<_Visitor>(__visitor),
                  ^
<source>:13:23: note: in instantiation of function template specialization 'std::visit<ABVis &, std::variant<A, B, C> &>' requested here
        decltype(std::visit(
                      ^
<source>:47:18: note: during template argument deduction for class template partial specialization 'can_visit<TVisitor, TVariant>' [with TVisitor = ABVis, TVariant = std::variant<A, B, C>]
    std::cout << can_visit<ABVis,ABC>::value << std::endl;
                 ^
<source>:47:18: note: in instantiation of template class 'can_visit<ABVis, std::variant<A, B, C>>' requested here
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:955:7: note: subexpression not valid in a constant expression
            (_S_apply_single_alt<false, __var_indices>(
             ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:940:2: note: in call to '_S_apply_all_alts(__vtable, {})'
        _S_apply_all_alts(
        ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1042:4: note: in call to '_S_apply()'
        = __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();
          ^
2 errors generated.
ASM generation compiler returned: 1
In file included from <source>:1:
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1020:11: error: no matching function for call to '__invoke'
          return std::__invoke(std::forward<_Visitor>(__visitor),
                 ^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1031:29: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &)>, std::integer_sequence<unsigned long, 2>>::__visit_invoke' requested here
      { return _Array_type{&__visit_invoke}; }
                            ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:976:48: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &)>, std::integer_sequence<unsigned long, 2>>::_S_apply' requested here
                std::index_sequence<__indices..., __index>>::_S_apply();
                                                             ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:955:7: note: in instantiation of function template specialization 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &), 3>, std::integer_sequence<unsigned long>>::_S_apply_single_alt<false, 2, std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &)>>' requested here
            (_S_apply_single_alt<false, __var_indices>(
             ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:940:2: note: in instantiation of function template specialization 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &), 3>, std::integer_sequence<unsigned long>>::_S_apply_all_alts<0, 1, 2>' requested here
        _S_apply_all_alts(
        ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1042:59: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(ABVis &, std::variant<A, B, C> &), 3>, std::integer_sequence<unsigned long>>::_S_apply' requested here
        = __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();
                                                                 ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1699:45: note: in instantiation of static data member 'std::__detail::__variant::__gen_vtable<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>::_S_vtable' requested here
        _Result_type, _Visitor&&, _Variants&&...>::_S_vtable;
                                                   ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1718:19: note: in instantiation of function template specialization 'std::__do_visit<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>' requested here
      return std::__do_visit<_Tag>(std::forward<_Visitor>(__visitor),
                  ^
<source>:13:23: note: in instantiation of function template specialization 'std::visit<ABVis &, std::variant<A, B, C> &>' requested here
        decltype(std::visit(
                      ^
<source>:47:18: note: during template argument deduction for class template partial specialization 'can_visit<TVisitor, TVariant>' [with TVisitor = ABVis, TVariant = std::variant<A, B, C>]
    std::cout << can_visit<ABVis,ABC>::value << std::endl;
                 ^
<source>:47:18: note: in instantiation of template class 'can_visit<ABVis, std::variant<A, B, C>>' requested here
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/bits/invoke.h:89:5: note: candidate template ignored: substitution failure [with _Callable = ABVis &, _Args = <C &>]: no type named 'type' in 'std::__invoke_result<ABVis &, C &>'
    __invoke(_Callable&& __fn, _Args&&... __args)
    ^
In file included from <source>:1:
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1041:36: error: constexpr variable '_S_vtable' must be initialized by a constant expression
      static constexpr _Array_type _S_vtable
                                   ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1699:45: note: in instantiation of static data member 'std::__detail::__variant::__gen_vtable<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>::_S_vtable' requested here
        _Result_type, _Visitor&&, _Variants&&...>::_S_vtable;
                                                   ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1718:19: note: in instantiation of function template specialization 'std::__do_visit<std::__detail::__variant::__deduce_visit_result<void>, ABVis &, std::variant<A, B, C> &>' requested here
      return std::__do_visit<_Tag>(std::forward<_Visitor>(__visitor),
                  ^
<source>:13:23: note: in instantiation of function template specialization 'std::visit<ABVis &, std::variant<A, B, C> &>' requested here
        decltype(std::visit(
                      ^
<source>:47:18: note: during template argument deduction for class template partial specialization 'can_visit<TVisitor, TVariant>' [with TVisitor = ABVis, TVariant = std::variant<A, B, C>]
    std::cout << can_visit<ABVis,ABC>::value << std::endl;
                 ^
<source>:47:18: note: in instantiation of template class 'can_visit<ABVis, std::variant<A, B, C>>' requested here
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:955:7: note: subexpression not valid in a constant expression
            (_S_apply_single_alt<false, __var_indices>(
             ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:940:2: note: in call to '_S_apply_all_alts(__vtable, {})'
        _S_apply_all_alts(
        ^
/opt/compiler-explorer/gcc-10.3.0/lib/gcc/x86_64-linux-gnu/10.3.0/../../../../include/c++/10.3.0/variant:1042:4: note: in call to '_S_apply()'
        = __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();
          ^
2 errors generated.
Execution build compiler returned: 1

For context it is not much different than this code https://godbolt.org/z/aa5cqWhz8

#include <variant>
#include <iostream>

template <class A, class B, class=void>
struct can_add: std::false_type {};

template <class A, class B >
struct can_add
<
    A, 
    B, 
    std::void_t<
        decltype(std::declval<A>()+std::declval<B>()
        )
    >
>
 : std::true_type {};


int main(){


    std::cout << can_add<int,int>::value << std::endl;
    std::cout << can_add<int,std::string>::value << std::endl;

    
}

which does compile and outputs

1
0

as expected.


Solution

  • The detection idiom can only determine whether the given expression is valid.

    And the expression

    std::visit(std::declval<TVisitor&>(), std::declval<TVariant&>())
    

    is valid for all TVisitor and TVariant, because std::visit is an unrestricted template1.

    However, the corresponding instantiation may not be valid, but this is not something you can detect directly.

    Instead, you'll have to manually detect the conditions that may make the instantiation invalid. Your type trait should determine whether the given visitor can be called with all types in the given variant.

    Like so, for instance:

    template <class TVisitor, class TVariant>
    struct can_visit;
    
    template <class TVisitor, class... TTypes>
    struct can_visit<TVisitor, std::variant<TTypes...>> {
        constexpr static bool value = (std::is_invocable_v<TVisitor, TTypes> && ...);
    };
    

    https://godbolt.org/z/fz7e5W7ah


    1 It's possible that this and this require std::visit to be SFINAE'd, but I don't know enough standardese to figure that out.