使用 C++ std::visit 时如何改进编译器错误消息?

jin*_*123 10 c++ compiler-errors std-variant

我在具有std::visit()许多替代方案的变体上使用 C++17 的函数,每当我忘记访问者中的一个或多个替代方案时,编译器生成的错误消息都非常难以理解。

例如

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

using Foo = std::variant<A, B, /* ... many more alternatives ... */>;

Foo foo;

std::visit(overloaded{
    [](A const& a) { /* ... */ },
    [](B const& b) { /* ... */ },
    /* ... forgot 1+ alternatives ... */
    }, foo
);
Run Code Online (Sandbox Code Playgroud)

在上面的代码示例中,编译器可以生成长度为数千个字符的错误消息,具体取决于替代方案的数量。有没有办法改进这些错误消息,以便编译器输出类似以下内容?

example.cc:8-13: error: Non-exhaustive visitor -- missing alternative of type 'X'
Run Code Online (Sandbox Code Playgroud)

jin*_*123 2

我第一次尝试解决这个问题可以在这里找到。经过一些谷歌搜索和大量的试验和错误后,我想出了一个更好的解决方案,我已将其发布在这里。为了方便起见,我将在下面复制粘贴解决方案。

\n
\n

这是一个概念证明。

\n
#include <iostream>\n#include <variant>\n\n\ntemplate <typename> class Test { };\n\nusing Foo = std::variant<\n    Test<struct A>,\n    Test<struct B>,\n    Test<struct C>,\n    Test<struct D>\n    >;\n\nusing Bar = std::variant<\n    Test<struct E>,\n    Test<struct F>,\n    Test<struct G>,\n    Test<struct H>,\n    Test<struct I>,\n    Test<struct J>,\n    Test<struct K>,\n    Test<struct L>\n    >;\n\n\ntemplate <typename T>\nstruct DefineVirtualFunctor\n{\n    virtual int operator()(T const&) const = 0;\n};\n\ntemplate <template <typename> typename Modifier, typename... Rest>\nstruct ForEach { };\ntemplate <template <typename> typename Modifier, typename T, typename... Rest>\nstruct ForEach<Modifier, T, Rest...> : Modifier<T>, ForEach<Modifier, Rest...> { };\n\ntemplate <typename Variant>\nstruct Visitor;\ntemplate <typename... Alts>\nstruct Visitor<std::variant<Alts...>> : ForEach<DefineVirtualFunctor, Alts...> { };\n\n\nstruct FooVisitor final : Visitor<Foo>\n{\n    int operator()(Test<A> const&) const override { return  0; }\n    int operator()(Test<B> const&) const override { return  1; }\n    int operator()(Test<C> const&) const override { return  2; }\n    int operator()(Test<D> const&) const override { return  3; }\n};\n\nstruct BarVisitor final : Visitor<Bar>\n{\n    int operator()(Test<E> const&) const override { return  4; }\n    int operator()(Test<F> const&) const override { return  5; }\n    int operator()(Test<G> const&) const override { return  6; }\n    int operator()(Test<H> const&) const override { return  7; }\n    int operator()(Test<I> const&) const override { return  8; }\n    int operator()(Test<J> const&) const override { return  9; }\n    int operator()(Test<K> const&) const override { return 10; }\n    int operator()(Test<L> const&) const override { return 11; }\n};\n\n\nint main(int argc, char const* argv[])\n{\n    Foo foo;\n    Bar bar;\n    \n    switch (argc) {\n    case  0: foo = Foo{ std::in_place_index<0> }; break;\n    case  1: foo = Foo{ std::in_place_index<1> }; break;\n    case  2: foo = Foo{ std::in_place_index<2> }; break;\n    default: foo = Foo{ std::in_place_index<3> }; break;\n    }\n    switch (argc) {\n    case  0: bar = Bar{ std::in_place_index<0> }; break;\n    case  1: bar = Bar{ std::in_place_index<1> }; break;\n    case  2: bar = Bar{ std::in_place_index<2> }; break;\n    case  3: bar = Bar{ std::in_place_index<3> }; break;\n    case  4: bar = Bar{ std::in_place_index<4> }; break;\n    case  5: bar = Bar{ std::in_place_index<5> }; break;\n    case  6: bar = Bar{ std::in_place_index<6> }; break;\n    default: bar = Bar{ std::in_place_index<7> }; break;\n    }\n    \n    std::cout << std::visit(FooVisitor{ }, foo) << "\\n";\n    std::cout << std::visit(BarVisitor{ }, bar) << "\\n";\n\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,Visitor类模板接受一个std::variant类型作为模板参数,它将定义一个接口,该接口必须在从模板类实例化继承的任何子类中实现。如果在子类中,您碰巧忘记重写纯虚方法之一,您将收到如下错误。

\n
$ g++ -std=c++17 -o example example.cc\nexample.cc: In function \xe2\x80\x98int main(int, const char**)\xe2\x80\x99:\nexample.cc:87:41: error: invalid cast to abstract class type \xe2\x80\x98BarVisitor\xe2\x80\x99\n   87 |     std::cout << std::visit(BarVisitor{ }, bar) << "\\n";\n      |                                         ^\nexample.cc:51:8: note:   because the following virtual functions are pure within \xe2\x80\x98BarVisitor\xe2\x80\x99:\n   51 | struct BarVisitor final : Visitor<Bar>\n      |        ^~~~~~~~~~\nexample.cc:29:17: note:     \xe2\x80\x98int DefineVirtualFunctor<T>::operator()(const T&) const [with T = Test<J>]\xe2\x80\x99\n   29 |     virtual int operator()(T const&) const = 0;\n      |                 ^~~~~~~~\n
Run Code Online (Sandbox Code Playgroud)\n

这比编译器在使用时通常生成的错误消息更容易理解std::visit()

\n