decltype и boost::variant
От: FrozenHeart  
Дата: 10.02.14 21:19
Оценка:
Здравствуйте.

Имеется следующий код:

#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?
avalon/1.0.433
Re: decltype и boost::variant
От: Кодт Россия  
Дата: 11.02.14 07:19
Оценка: +2
Здравствуйте, 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;
  }
}

Гугли бустовскую доку по выделенным словам.
Перекуём баги на фичи!
Re[2]: decltype и boost::variant
От: FrozenHeart  
Дата: 11.02.14 14:15
Оценка:
К> А с каких пор можно тип функции задавать по значению времени выполнения?!

gcc 4.8 говорит

sorry, unimplemented: mangling typeid_expr


но не ругается на сам код. Или я Вас не так понял?

К> Паттерн Посетитель.


Да, об этом я в курсе. Но тут приходится в итоге явно указывать тип того, что хранится на данный момент в объекте класса boost::variant. Мне же было бы достаточно, не зная типа объекта, просто получить его и написать, например, в лог.
avalon/1.0.433
Re[3]: decltype и boost::variant
От: jazzer Россия Skype: enerjazzer
Дата: 11.02.14 14:35
Оценка:
Здравствуйте, 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; }
};
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: decltype и boost::variant
От: Кодт Россия  
Дата: 11.02.14 18:37
Оценка:
Здравствуйте, 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);
  // типы аргументов меняться не будут, а вот список параметров мы раскрутим
}
Перекуём баги на фичи!
Re[4]: decltype и boost::variant
От: Кодт Россия  
Дата: 12.02.14 08:47
Оценка:
Чтобы не тащить велосипед в продакшен, предлагаю задействовать всё тот же посетитель и для диспетчеризации.
Логика у нас всё та же: если тип совпадает с требуемым — выполняем работу, если не совпадает — отстреливаемся.
#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 }
Перекуём баги на фичи!
Re: decltype и boost::variant
От: TarasB  
Дата: 12.02.14 08:57
Оценка: :))
Здравствуйте, FrozenHeart, Вы писали:

FH>Здравствуйте.


FH>Имеется следующий код:

FH> if (typeid(FirstArg) == variant_type_info)
FH> {
FH> return boost::get<FirstArg>(variant);
FH> }

FH> return bar<OtherArgs...>(variant_type_info);



Чтоэта?
Динамическая питузация в моём уютненьком С++?!
Валите в шарпик или в жабаскриптик с такими приколами!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.