我在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
);
在上面的代碼示例中,編譯器可以生成長度為數千個字符的錯誤訊息,具體取決于備選方案的數量。有沒有辦法改進這些錯誤訊息,以便編譯器輸出如下內容?
example.cc:8-13: error: Non-exhaustive visitor -- missing alternative of type 'X'
uj5u.com熱心網友回復:
IMO您可以將多載集包裝在一個函式物件中,該函式物件在錯過的情況下執行默認例程(很像陳述句default中的部分)。switch我把默認多載放在開頭,以免忘記:
auto any_visitor=[](auto&& val, auto&& default_fn,auto ...fn){
overloaded vis{fn ...};
if constexpr(std::is_invokable_v<decltype(vis), decltype(val)>)
return vis(std::forward(val));
else
return std::invoke(std::forward(default_fn), std::forward(val));
};
std::visit(
std::bind_back(
any_visitor,
[](auto&&){
/* default visitor logic for missed cases*/
},
[](A const& a) { /* ... */ },
[](B const& b) { /* ... */ },
/* ... forgot 1 alternatives ... */
}),
foo
);
uj5u.com熱心網友回復:
我第一次嘗試解決這個問題可以在這里找到。經過一些谷歌搜索和大量試驗和錯誤,我想出了一個更好的解決方案,我已經發布在這里。為方便起見,我將在下面復制粘貼解決方案。
這是一個概念證明。
#include <iostream>
#include <variant>
template <typename> class Test { };
using Foo = std::variant<
Test<struct A>,
Test<struct B>,
Test<struct C>,
Test<struct D>
>;
using Bar = std::variant<
Test<struct E>,
Test<struct F>,
Test<struct G>,
Test<struct H>,
Test<struct I>,
Test<struct J>,
Test<struct K>,
Test<struct L>
>;
template <typename T>
struct DefineVirtualFunctor
{
virtual int operator()(T const&) const = 0;
};
template <template <typename> typename Modifier, typename... Rest>
struct ForEach { };
template <template <typename> typename Modifier, typename T, typename... Rest>
struct ForEach<Modifier, T, Rest...> : Modifier<T>, ForEach<Modifier, Rest...> { };
template <typename Variant>
struct Visitor;
template <typename... Alts>
struct Visitor<std::variant<Alts...>> : ForEach<DefineVirtualFunctor, Alts...> { };
struct FooVisitor final : Visitor<Foo>
{
int operator()(Test<A> const&) const override { return 0; }
int operator()(Test<B> const&) const override { return 1; }
int operator()(Test<C> const&) const override { return 2; }
int operator()(Test<D> const&) const override { return 3; }
};
struct BarVisitor final : Visitor<Bar>
{
int operator()(Test<E> const&) const override { return 4; }
int operator()(Test<F> const&) const override { return 5; }
int operator()(Test<G> const&) const override { return 6; }
int operator()(Test<H> const&) const override { return 7; }
int operator()(Test<I> const&) const override { return 8; }
int operator()(Test<J> const&) const override { return 9; }
int operator()(Test<K> const&) const override { return 10; }
int operator()(Test<L> const&) const override { return 11; }
};
int main(int argc, char const* argv[])
{
Foo foo;
Bar bar;
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;
default: foo = Foo{ std::in_place_index<3> }; break;
}
switch (argc) {
case 0: bar = Bar{ std::in_place_index<0> }; break;
case 1: bar = Bar{ std::in_place_index<1> }; break;
case 2: bar = Bar{ std::in_place_index<2> }; break;
case 3: bar = Bar{ std::in_place_index<3> }; break;
case 4: bar = Bar{ std::in_place_index<4> }; break;
case 5: bar = Bar{ std::in_place_index<5> }; break;
case 6: bar = Bar{ std::in_place_index<6> }; break;
default: bar = Bar{ std::in_place_index<7> }; break;
}
std::cout << std::visit(FooVisitor{ }, foo) << "\n";
std::cout << std::visit(BarVisitor{ }, bar) << "\n";
return 0;
}
如您所見,Visitor類模板接受一個std::variant型別作為模板引數,它將定義一個介面,該介面必須在繼承自模板類實體化的任何子類中實作。如果在子類中,您碰巧忘記覆寫純虛方法之一,您將收到如下錯誤。
$ g -std=c 17 -o example example.cc
example.cc: In function ‘int main(int, const char**)’:
example.cc:87:41: error: invalid cast to abstract class type ‘BarVisitor’
87 | std::cout << std::visit(BarVisitor{ }, bar) << "\n";
| ^
example.cc:51:8: note: because the following virtual functions are pure within ‘BarVisitor’:
51 | struct BarVisitor final : Visitor<Bar>
| ^~~~~~~~~~
example.cc:29:17: note: ‘int DefineVirtualFunctor<T>::operator()(const T&) const [with T = Test<J>]’
29 | virtual int operator()(T const&) const = 0;
| ^~~~~~~~
這比編譯器在使用std::visit().
uj5u.com熱心網友回復:
我想出了一個不太理想的解決方案,但總比沒有好。如果最終出現更好的解決方案,我會很樂意將接受的答案轉換為那個。
這是一個概念證明。
#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
);
}
使用-fmax-errors=1(GCC) 或-ferror-limit=1(Clang) 編譯時,STD_VISIT_IMPROVE_COMPILER_ERRORS_LAMBDA會列印出靜態斷言訊息,解釋錯誤。然而不幸的是,它并沒有告訴我們哪個替代方案不滿意,它仍然不能防止生成原始的、冗長的、實際上難以理解的編譯器錯誤。不過,至少,錯誤的原因更清楚了。
例如
$ 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.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/486138.html
