I've come up with a less-than-ideal solution, but it's better than nothing. If a better solution eventually comes along, I will happily switch the accepted answer to that.
Here's a proof of concept.
#include <variant>
#define STD_VISIT_IMPROVE_COMPILER_ERRORS_LAMBDA \
[](auto... __args) { \
static_assert(always_false_v<decltype(__args)...>, "non-exhaustive visitor"); \
},
template <typename... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template <typename> constexpr bool always_false_v = false;
template <typename> class Test { };
using Foo = std::variant<
std::monostate,
Test<struct A>,
Test<struct B>,
Test<struct C>,
Test<struct D>,
Test<struct E>,
Test<struct F>,
Test<struct G>,
Test<struct H>,
Test<struct I>,
Test<struct J>,
Test<struct K>,
Test<struct L>,
Test<struct M>,
Test<struct N>,
Test<struct O>,
Test<struct P>,
Test<struct Q>,
Test<struct R>,
Test<struct S>,
Test<struct T>,
Test<struct U>,
Test<struct V>,
Test<struct W>,
Test<struct X>,
Test<struct Y>,
Test<struct Z>
>;
int main(int argc, char const* argv[])
{
Foo foo;
switch (argc) {
case 0: foo = Foo{ std::in_place_index< 0> }; break;
case 1: foo = Foo{ std::in_place_index< 1> }; break;
case 2: foo = Foo{ std::in_place_index< 2> }; break;
case 3: foo = Foo{ std::in_place_index< 3> }; break;
case 4: foo = Foo{ std::in_place_index< 4> }; break;
case 5: foo = Foo{ std::in_place_index< 5> }; break;
case 6: foo = Foo{ std::in_place_index< 6> }; break;
case 7: foo = Foo{ std::in_place_index< 7> }; break;
case 8: foo = Foo{ std::in_place_index< 8> }; break;
case 9: foo = Foo{ std::in_place_index< 9> }; break;
case 10: foo = Foo{ std::in_place_index<10> }; break;
case 11: foo = Foo{ std::in_place_index<11> }; break;
case 12: foo = Foo{ std::in_place_index<12> }; break;
case 13: foo = Foo{ std::in_place_index<13> }; break;
case 14: foo = Foo{ std::in_place_index<14> }; break;
case 15: foo = Foo{ std::in_place_index<15> }; break;
case 16: foo = Foo{ std::in_place_index<16> }; break;
case 17: foo = Foo{ std::in_place_index<17> }; break;
case 18: foo = Foo{ std::in_place_index<18> }; break;
case 19: foo = Foo{ std::in_place_index<19> }; break;
case 20: foo = Foo{ std::in_place_index<20> }; break;
case 21: foo = Foo{ std::in_place_index<21> }; break;
case 22: foo = Foo{ std::in_place_index<22> }; break;
case 23: foo = Foo{ std::in_place_index<23> }; break;
case 24: foo = Foo{ std::in_place_index<24> }; break;
case 25: foo = Foo{ std::in_place_index<25> }; break;
default: foo = Foo{ std::in_place_index<26> }; break;
}
return std::visit(overloaded{
[](std::monostate) { return 0; },
[](Test<A> const&) { return 1; },
[](Test<B> const&) { return 2; },
[](Test<C> const&) { return 3; },
[](Test<D> const&) { return 4; },
[](Test<E> const&) { return 5; },
[](Test<F> const&) { return 6; },
[](Test<G> const&) { return 7; },
[](Test<H> const&) { return 8; },
[](Test<I> const&) { return 9; },
[](Test<J> const&) { return 10; },
[](Test<K> const&) { return 11; },
[](Test<L> const&) { return 12; },
[](Test<M> const&) { return 13; },
[](Test<N> const&) { return 14; },
[](Test<O> const&) { return 15; },
[](Test<P> const&) { return 16; },
[](Test<Q> const&) { return 17; },
[](Test<R> const&) { return 18; },
[](Test<S> const&) { return 19; },
[](Test<T> const&) { return 20; },
[](Test<U> const&) { return 21; },
[](Test<V> const&) { return 22; },
[](Test<W> const&) { return 23; },
// [](Test<X> const&) { return 24; }, // Whoops...
[](Test<Y> const&) { return 25; },
[](Test<Z> const&) { return 26; },
STD_VISIT_IMPROVE_COMPILER_ERRORS_LAMBDA
}, foo
);
}
When compiling with -fmax-errors=1
(GCC) or -ferror-limit=1
(Clang), the STD_VISIT_IMPROVE_COMPILER_ERRORS_LAMBDA
causes the static assert message to print out, explaining the error. Unfortunately, however, it does not tell us which alternative is unsatisfied and it still does not prevent the original, long and practically-unintelligible compiler error from being generated. At least, though, the cause of the error is more clear.
e.g.
$ g++ -std=c++17 -fmax-errors=1 -o example example.cc
...
example.cc:5:19: error: static assertion failed: non-exhaustive visitor
5 | static_assert(always_false_v<decltype(__args)...>, "non-exhaustive visitor"); \
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cc:107:9: note: in expansion of macro ‘STD_VISIT_IMPROVE_COMPILER_ERRORS_LAMBDA’
107 | STD_VISIT_IMPROVE_COMPILER_ERRORS_LAMBDA
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -fmax-errors=1.
visit
- no. But even though the errors are horrible, there aren't too many possible causes: either a missing alternative or a mismatching return type. – Destructg++
,clang++
andMSVC
running at the same time. One of them will hopefully give an error message that is understandable :-) – Contradictionstd::visit()
? – Heyes