Здравствуйте.
Имеется следующий код:
#include <boost/variant.hpp>
#include <iostream>
#include <string>
boost::variant<int, double, std::string> variant;
template <typename FirstArg, typename... OtherArgs>
auto bar(const std::type_info& variant_type_info) -> decltype(typeid(FirstArg) == variant_type_info ? boost::get<FirstArg>(variant) : bar<OtherArgs...>(variant_type_info))
{
if (typeid(FirstArg) == variant_type_info)
{
return boost::get<FirstArg>(variant);
}
return bar<OtherArgs...>(variant_type_info);
}
template <typename... VariantArgs>
auto foo(const boost::variant<VariantArgs...>& variant) -> decltype(bar<VariantArgs...>(variant.type()))
{
return bar<VariantArgs...>(variant.type());
}
int main()
{
variant = 0.5;
const auto& baz = foo(variant);
std::cout << baz << '\n';
}
gcc 4.8
выдаёт на него следующие ошибки компиляции:
main.cpp:9:135: error: 'bar' was not declared in this scope
auto bar(const std::type_info& variant_type_info) -> decltype(typeid(FirstArg) == variant_type_info ? boost::get<FirstArg>(variant) : bar<OtherArgs...>(variant_type_info))
^
main.cpp:9:148: error: expected primary-expression before '...' token
auto bar(const std::type_info& variant_type_info) -> decltype(typeid(FirstArg) == variant_type_info ? boost::get<FirstArg>(variant) : bar<OtherArgs...>(variant_type_info))
^
main.cpp:9:148: error: expected ')' before '...' token
main.cpp:9:135: error: 'bar' was not declared in this scope
auto bar(const std::type_info& variant_type_info) -> decltype(typeid(FirstArg) == variant_type_info ? boost::get<FirstArg>(variant) : bar<OtherArgs...>(variant_type_info))
^
main.cpp:9:54: error: expected type-specifier before 'decltype'
auto bar(const std::type_info& variant_type_info) -> decltype(typeid(FirstArg) == variant_type_info ? boost::get<FirstArg>(variant) : bar<OtherArgs...>(variant_type_info))
^
main.cpp:9:54: error: expected initializer before 'decltype'
main.cpp:20:69: error: 'bar' was not declared in this scope
auto foo(const boost::variant<VariantArgs...>& variant) -> decltype(bar<VariantArgs...>(variant.type()))
^
main.cpp:20:69: error: 'bar' was not declared in this scope
main.cpp:20:84: error: expected primary-expression before '...' token
auto foo(const boost::variant<VariantArgs...>& variant) -> decltype(bar<VariantArgs...>(variant.type()))
^
main.cpp:20:84: error: expected ')' before '...' token
main.cpp:20:69: error: 'bar' was not declared in this scope
auto foo(const boost::variant<VariantArgs...>& variant) -> decltype(bar<VariantArgs...>(variant.type()))
^
main.cpp:20:69: error: 'bar' was not declared in this scope
main.cpp:20:60: error: expected type-specifier before 'decltype'
auto foo(const boost::variant<VariantArgs...>& variant) -> decltype(bar<VariantArgs...>(variant.type()))
^
main.cpp:20:60: error: expected initializer before 'decltype'
main.cpp: In function 'int main()':
main.cpp:28:34: error: 'foo' was not declared in this scope
const auto& baz = foo(variant);
Я понимаю, что дело в том, что я обращаюсь к той же самой функции в decltype. Существуют ли какие-нибудь workaround'ы на данную тему? Или можно ли как-то по-дргому решить задачу получения текущего значения объекта класса boost::variant?
Здравствуйте, FrozenHeart, Вы писали:
FH>template <typename FirstArg, typename... OtherArgs>
FH>auto bar(const std::type_info& variant_type_info) -> decltype(typeid(FirstArg) == variant_type_info ? boost::get<FirstArg>(variant) : bar<OtherArgs...>(variant_type_info))
А с каких пор можно тип функции задавать по значению времени выполнения?!
FH>Я понимаю, что дело в том, что я обращаюсь к той же самой функции в decltype. Существуют ли какие-нибудь workaround'ы на данную тему? Или можно ли как-то по-дргому решить задачу получения текущего значения объекта класса boost::variant?
Паттерн Посетитель.
#include <iostream>
#include <string>
#include <boost/variant.hpp>
typedef boost::variant<int,double,std::string> something;
struct printer : boost::static_visitor<void>
{
template<class T> void operator()(T const& t) const { std::cout << t << std::endl; }
};
struct drucker : boost::static_visitor<int>
{
int operator()(int x) const { std::cout << "number-like: " << x << std::endl; return x; }
int operator()(std::string const& x) const { std::cout << "string-like: " << x << std::endl; return atoi(x.c_str()); }
};
int main()
{
something x[3] = {(1), (2.3), ("4.56")};
for(int i=0; i!=3; ++i)
{
something const& s = x[i];
boost::apply_visitor(printer(),s);
int n = boost::apply_visitor(drucker(),s);
std::cout << n << std::endl << std::endl;
}
}
Гугли бустовскую доку по выделенным словам.
К> А с каких пор можно тип функции задавать по значению времени выполнения?!
gcc 4.8
говорит
sorry, unimplemented: mangling typeid_expr
но не ругается на сам код. Или я Вас не так понял?
К> Паттерн Посетитель.
Да, об этом я в курсе. Но тут приходится в итоге явно указывать тип того, что хранится на данный момент в объекте класса boost::variant. Мне же было бы достаточно, не зная типа объекта, просто получить его и написать, например, в лог.
Здравствуйте, FrozenHeart, Вы писали:
FH>Да, об этом я в курсе. Но тут приходится в итоге явно указывать тип того, что хранится на данный момент в объекте класса boost::variant. Мне же было бы достаточно, не зная типа объекта, просто получить его и написать, например, в лог.
вот этот посетитель именно это и делает, с любым вариантом, и в С++98:
struct printer : boost::static_visitor<void>
{
template<class T> void operator()(T const& t) const { std::cout << t << std::endl; }
};
Здравствуйте, FrozenHeart, Вы писали:
К>> А с каких пор можно тип функции задавать по значению времени выполнения?!
FH>sorry, unimplemented: mangling typeid_expr
FH>но не ругается на сам код. Или я Вас не так понял?
Ну тут сразу две ошибки.
Первая — концептуальная, как я уже сказал: попытка из рантайма (type_info const&) получить тип функции.
Какой-то тип у decltype(#####), конечно, будет, — но зафиксируется он во время компиляции.
Поскольку там тернарный оператор, то decltype(condition ? alfa : beta) превратится в тип, общий для alfa и beta, если такое вообще возможно.
Конкретно для int и double это будет double, а для double и string будет ошибка компиляции.
Вторая ошибка — незавершённая рекурсия на списке параметров.
template<class First, class... Others> auto foo() -> decltype(##### foo<Others...> #####)
Получается, что foo<int,double,string> зависит от foo<double,string>;
foo<double,string> — от foo<string>;
foo<string> — от foo<>;
а для foo<> шаблона не существует: тот, который есть, требует хотя бы одного параметра.
Формально код правильный, хотя и бесполезный.
Поскольку в итоге компилятор выяснит, что он не может сконструировать выражение этого тернарного оператора, то decltype не существует, и сработает SFINAE. То есть, этот шаблон будет признан неподходящим.
А других сигнатур foo у нас нет, поэтому при попытке воплощения шаблона bar будет уже полноценная ошибка компиляции.
Но если не пытаться воплощать bar, вызывая его из main, — то проблема не обнаружится.
Выходом могло бы быть определение foo для единственного параметра
template<class OnlyOne> auto foo() -> OnlyOne /* наверное? */
{
return variant::get<OnlyOne>();
}
Чтобы не отстрелилось по SFINAE уже на несовместимости аргументов тернарного оператора, — тоже можно что-нибудь придумать.
Ну там всяких хелперов, приводящих строки к числам, а числа — тоже к числам.
Но повторюсь, это концептуально бессмысленно.
В лучшем случае мы могли бы реализовать приведение всех типов-участников варианта к какому-то общему знаменателю.
К>> Паттерн Посетитель.
FH>Да, об этом я в курсе. Но тут приходится в итоге явно указывать тип того, что хранится на данный момент в объекте класса boost::variant. Мне же было бы достаточно, не зная типа объекта, просто получить его и написать, например, в лог.
Всё равно нужен посетитель.
Что-то вот такое
template<class Executor, class Result, class Variant, class X, class... Xs>
auto call_by_id__impl(Executor& e, std::type_info const& t, Variant const& v) -> Result
{
if(typeid(X) == t)
return e(boost::get<X>(v));
else
return call_by_id__impl<Executor, Result, Variant, Xs...>(e, t, v);
}
// и последний шаг
template<class Executor, class Result, class Variant>
auto call_by_id__impl(Executor& e, std::type_info const& t, Variant const& v) -> Result
{
throw WTF! type info out of the list
}
// ну и запуск вывода типов
template<class Executor, class... Xs>
auto call_by_id(Executor& e, std::type_info const& t, boost::variant<Xs...> const& v) -> typename boost::result_of<Executor>::type
{
return call_by_id__impl<Executor, typename boost::result_of<Executor>::type, Xs...>(e,t,v);
// типы аргументов меняться не будут, а вот список параметров мы раскрутим
}
Чтобы не тащить велосипед в продакшен, предлагаю задействовать всё тот же посетитель и для диспетчеризации.
Логика у нас всё та же: если тип совпадает с требуемым — выполняем работу, если не совпадает — отстреливаемся.
#include <iostream>
#include <string>
#include <boost/variant.hpp>
#include <boost/utility.hpp>
#include <typeinfo>
#include <stdexcept>
typedef boost::variant<int,double,std::string> something;
// функция из предыдущих примеров - "записать в лог"
struct printer : boost::static_visitor<void>
{
template<class T> void operator()(T const& t) const { std::cout << t << std::endl; }
};
// а вот и наш посетитель
template<class R, class F> // параметризованный целевой функцией
struct iftype_t : boost::static_visitor<R> // что-то туплю, как из возможно полиморфной функции F выковырять общий возвращаемый тип
{
F const& f;
std::type_info const& ti;
iftype_t(F const& f, std::type_info const& ti) : f(f), ti(ti) {}
template<class T> auto operator()(T const& t) const -> R
{
if(typeid(T) == ti)
return f(t);
else
throw std::runtime_error("bad type"); // мне не хватило фантазии придумать, что кидать - пусть будет пока это.
}
};
// порождающая функция
template<class R, class F> iftype_t<R,F> iftype(F const& f, std::type_info const& ti) { return iftype_t<R,F>(f,ti); }
int main()
{
something vars[3] = {(1), (2.3), ("4.56")};
for(int i=0; i!=3; ++i)
{
something const& var = vars[i];
boost::apply_visitor(printer(),var); // вызываем функцию безусловно
try
{
boost::apply_visitor(iftype<void>(printer(), typeid(int)), var); // вызываем функцию условно
}
catch(...)
{
std::cout << "bad" << std::endl;
}
std::cout << std::endl << std::endl;
}
}
Полиморфные лямбды позволят нам обойтись без таких выкрутасов, но это в следующих сериях компиляторов...
boost::apply_visitor( [](auto x)->void { if(typeid(x)==typeid(int)..... }, s }