Почему-то boost::variant
избегает ошибки.
Да. Вариант Boost имеет поддержку распространения атрибутов.
Кроме того, boost::variant
имеет специальную обработку boost::recursive_wrapper
, так что это может быть двойной запретный полет.
Хорошая статья о рекурсии std::variant
s здесь https://vittorioromeo.info/index/blog/variants_lambdas_part_2.html
Что не так с boost::variant
?
Если вы хотите, вы можете написать некоторые черты трансформации или даже посмотреть на x3 :: вариант - он может подойти вам лучше?
Live On Coliru
#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
struct Recurse;
using Base = x3::variant<
std::string,
x3::forward_ast<Recurse> >;
struct Recurse
{
int _i;
Base _base;
};
BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;
BOOST_SPIRIT_DEFINE(base)
int main()
{
std::string text;
Base result;
x3::phrase_parse(std::begin(text), std::end(text), base, ascii::space, result);
return 0;
}
Примечание: нет x3::forward_ast<>
не помогает с std::variant
, подтверждая, что std::variant
просто не хватает поддержки в x3
ОБНОВЛЕНИЕ
Вы можете обойти все, сделав Base
производной структурой с необходимым механизмом, чтобы указать Духу, что это вариант (и над какими типами). Таким образом, вам не нужно go через ад специализации черт:
struct Recurse;
struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
using BaseV::BaseV;
using BaseV::operator=;
struct adapted_variant_tag {};
using types = boost::mpl::list<std::string, Recurse>;
};
struct Recurse {
int _i;
Base _base;
};
Как вы можете видеть, это в основном то же самое, но добавляет adapted_variant_tag
и types
вложенных типов.
Обратите внимание , что, умело жестко закодировав последовательность types
, мы можем притворно обращаться с рекурсивной оболочкой. Нам повезло, что этого достаточно, чтобы обмануть систему.
Добавление некоторых отладочных выходных данных и тестовых случаев:
Live On Coliru
#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
namespace { // for debug
template<class T>
std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
return os << rw.get();
}
template<class... Ts>
std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
std::visit([&os](const auto& v) { os << v; }, sv);
return os;
}
}
struct Recurse;
struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
using BaseV::BaseV;
using BaseV::operator=;
struct adapted_variant_tag {};
using types = boost::mpl::list<std::string, Recurse>;
};
struct Recurse {
int _i;
Base _base;
friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
return os << "[" << r._i << ", " << r._base << "]";
}
};
BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;
BOOST_SPIRIT_DEFINE(base)
int main()
{
for (std::string const text : { "yeah8", "32 more" }) {
Base result;
auto f = begin(text), l = end(text);
if (x3::phrase_parse(f, l, base, ascii::space, result)) {
std::cout << "Result: " << result << "\n";
} else {
std::cout << "Failed\n";
}
if (f!=l) {
std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
}
}
}
Какие отпечатки
Result: yeah8
Result: [32, more]
Обновление 2: Обледенение торта
Вот черты, необходимые для того, чтобы std::variant
просто работал:
namespace boost::spirit::x3::traits {
template<typename... t>
struct is_variant<std::variant<t...> >
: mpl::true_ {};
template <typename attribute, typename... t>
struct variant_has_substitute_impl<std::variant<t...>, attribute>
{
typedef std::variant<t...> variant_type;
typedef typename mpl::transform<
mpl::list<t...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, attribute>>,
mpl::identity<iter_1>
>::type
iter;
typedef mpl::not_<is_same<iter, end>> type;
};
template <typename attribute, typename... t>
struct variant_find_substitute<std::variant<t...>, attribute>
{
typedef std::variant<t...> variant_type;
typedef typename mpl::transform<
mpl::list<t...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, attribute> >,
mpl::identity<iter_1>
>::type
iter;
typedef typename
mpl::eval_if<
is_same<iter, end>,
mpl::identity<attribute>,
mpl::deref<iter>
>::type
type;
};
template <typename... t>
struct variant_find_substitute<std::variant<t...>, std::variant<t...> >
: mpl::identity<std::variant<t...> > {};
}
Это много шума, но вы можете убрать его где-нибудь в заголовке.
БОНУС
Исправление грамматики:
- вы, вероятно, имели в виду чтобы иметь
lexeme[]
вокруг строки - вы, вероятно, хотели иметь минимальную длину строки (+ char_, а не * char_), видя, что нет разделителей
- , которые могут иметься переупорядочить ветви, потому что производство строк сожрало бы целые числа для повторяющихся правил.
Вот мой исправленный взгляд на грамматику, где правила близко отражают AST, как обычно имеет смысл:
namespace Parser {
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
const auto recurse = x3::int_ >> base;
const auto base_def = recurse | string;
BOOST_SPIRIT_DEFINE(base)
}
Упрощение Fusion
Последнее, но не менее важное, в эру C ++ 11 вы можете сделать вывод адаптированные члены Fusion:
BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)
Live Full Demo
Live On Coliru
#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
namespace { // for debug
template<class T>
std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
return os << rw.get();
}
template<class... Ts>
std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
std::visit([&os](const auto& v) { os << v; }, sv);
return os;
}
}
struct Recurse;
using Base = std::variant<
std::string,
boost::recursive_wrapper<Recurse> >;
namespace boost::spirit::x3::traits {
template<typename... T>
struct is_variant<std::variant<T...> >
: mpl::true_ {};
template <typename Attribute, typename... T>
struct variant_has_substitute_impl<std::variant<T...>, Attribute>
{
typedef std::variant<T...> variant_type;
typedef typename mpl::transform<
mpl::list<T...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, Attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute>>,
mpl::identity<iter_1>
>::type
iter;
typedef mpl::not_<is_same<iter, end>> type;
};
template <typename Attribute, typename... T>
struct variant_find_substitute<std::variant<T...>, Attribute>
{
typedef std::variant<T...> variant_type;
typedef typename mpl::transform<
mpl::list<T...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, Attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute> >,
mpl::identity<iter_1>
>::type
iter;
typedef typename
mpl::eval_if<
is_same<iter, end>,
mpl::identity<Attribute>,
mpl::deref<iter>
>::type
type;
};
template <typename... T>
struct variant_find_substitute<std::variant<T...>, std::variant<T...> >
: mpl::identity<std::variant<T...> > {};
}
static_assert(x3::traits::is_variant<Base>{}, "");
struct Recurse
{
int _i;
Base _base;
friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
return os << "[" << r._i << ", " << r._base << "]";
}
};
BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)
namespace Parser {
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
const auto recurse = x3::int_ >> base;
const auto base_def = recurse | string;
BOOST_SPIRIT_DEFINE(base)
}
int main()
{
for (std::string const text : { "yeah8", "32 more", "18 766 most" }) {
Base result;
auto f = begin(text), l = end(text);
if (x3::phrase_parse(f, l, Parser::base, ascii::space, result)) {
std::cout << "Result: " << result << "\n";
} else {
std::cout << "Failed\n";
}
if (f!=l) {
std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
}
}
}
Какие отпечатки:
Result: yeah8
Result: [32, more]
Result: [18, [766, most]]
¹ (тонкая разница МОЖЕТ укусить вас в программировании generi c, где вам необходим явный доступ к базовому классу)