Здравствуйте, gbear, Вы писали:
G>>>А явно это как? Особенно интересно, как это "явно" будет выглядеть в обобщенном коде.
EP>>Например simplify(v) или какой-нибудь унарный оператор ^^ v
G>Да я не про это. Кто будет "дергать за веревочку"?
Программист вестимо, а кто ещё если это "явное" упрощение?
G>И главное — как он будет определять нужно ли "дергать"?
Самому или с помощью компилятора. Например сейчас же компилятор ругается на вывод Object'а?
G>Есть foo(x) -> void и bar(x) -> variant[int, void].
G>G>if(x > 0) bar(x) else foo(x)
G>
G>Нужно "дергать"?
Если неявного упрощения нет — то да, можно.
G>G>if(x > 0) bar(x)
G>
G>А теперь?
И здесь можно.
G>G>if(x > 0)
G> if(x > 10) x else foo(x)
G>
G>А если так?
И здесь.
G>>>А если автоматически, то получается тип Variant<Variant<T, void>, void> существовать не может?
Я вас правильно понимаю?
EP>>Хм, наверное да. Точнее существовать может, но до первого использования.
G>Плохо это. Потеряем возможность поймать "go wrong". Variant<Variant<T, void>, void> вполне может означать — что все ок... ну просто, например, вложеный if-c-else вычислился в void.
При неявном упрощении, поймать "go wrong" можно во внутренней вложенности.
G>Вывод, нужен таки maybe, а не суррогаты
Вывод: необходимо определится нужно ли неявное упрощение или нет. Как и всегда, у explicit и implicit есть свои плюс и минусы.
При этом у нас всегда есть возможность вернуться к текущему поведению Nemerle позвав специальную функцию/оператор.
G>G>def a = if(x > 0) "a" else 1; // "абстракная база" в виде какого-нибудь IConvertable
G>def b = if(a is string) stringToX(a) else intToX(a); // Вполне конкретный X
G>
G>В вашем случае: b — это в лучшем случае variant[X]. Что дальше?
При отсутствии неявного упрощения —
simplifiy(b)
G>Можно и по сложней примеры нарисовать — например, когда "база" таки нужна. Но, в вашем варианте получим очередной variant[T, U]
.
А в этом случае
deduce_base(b) 
Я уже неоднократно говорил, что вернуться от variant'а к текущему поведению всегда возможно через спец вызов, а вот наоборот уже нет.
G>Понимаете, то, что в данном случае диспетчеризация — скорее всего... этожплюсы, в конце концов — статическая (не виртуальная, если хотите) — ничего не меняет.
Там, по сути, внутри union + tag, по тэгу делается один switch, в ветках case делается cast к текущему типу и вызов visitor'а.
Если хотите называть простой вызов конкретной функции, у объекта конкретного типа, где не делается никакой выбор, даже compile time (хотя бы что-то типа category dispatch как в std::distance) dispatch'ем — то тогда вам придётся и все методы типа std::vector<T>::push_back называть single dispatch'ем.
Да, бывает compile time диспетчеризация, как в std::distance, перегрузках, специализациях и т.п. Но тут-то ничего этого нет.
G>>>Что мешает-то на лету "собрать" нужный тип (в вашем примере, это агрегат Widget и Gadget), реализующий _нужный_ интерфейс?!
EP>>Пример сборки нужного типа в студию. Мне кажется это будет сложнее чем variant.
G>А в чем проблема? Я такой тип вам и в C# соберу "налету" — только в run-time. В чем проблемы то?! Нам же нужен только агригат (контейней, если хотите), который делегирует вызовы конкретного интерфейса, конкретному экземпляру.
Проблема в том, что вместо использования уже готовых возможностей:
Variant<Widget, Gadget> x, y;
// ...
apply(x, y, [](auto &x, auto &y)
{
draw(x);
draw(y);
collide(x, y);
});
вы собрались собирать какой-то тип, непонятно как, и непонятно зачем.
В коде выше apply выбирает выводит конкретные типы для x и y однократно, на основе их тэгов, и внутри лямбды нет никаких проверок тэгов. В том варианте который предлагаете вы, как я понял — на каждом вызове типа draw будет что-то типа virtual call.
G>В Nemerle — такой же фокус можно провернуть в compile-time. А сложность тут вообще не причем... этот код пишется ровно один раз.
Ок, допустим один раз. Что будет сгенерировано в качестве агрегата для
Variant<Widget, Gadget> из примера выше? Какой интерфейс у этого агрегата?
EP>>Тем что такой синтаксис можно получить на базе boost::variant
G>Синтаксис-то тут причем?! Скажите лучше во что это вычислится? В variant?
Да, в чём проблема-то? В общую базу это нормально, а в variant — нет? Почему?
В следующем примере MATCH вычисляется в variant:
LIVE DEMO
#include <iterator>
#include <vector>
#include "match.hpp"
struct widget {};
struct gadget {};
void draw(widget) { println("drawing widget"); }
void draw(gadget) { println("drawing gadget"); }
int main()
{
using namespace std;
bool flags[] = {true, false, true, true, false};
vector<variant<widget, gadget>> xs;
for(auto f : flags)
if(f)
xs.push_back(widget{});
else
xs.push_back(gadget{});
for(auto x : xs)
MATCH(x)
CASE(x IS _) { draw(x); }
;
println();
vector<variant<int, double>> ys;
for(auto x : xs)
ys.push_back
(
MATCH(x)
CASE(x IS widget) { println("x IS widget"); return 1; }
CASE(x IS gadget) { println("x IS gadget"); return 0.1; }
);
println();
for(auto y : ys)
MATCH(y)
CASE(y IS int) { println("y IS int"); }
CASE(y IS double) { println("y IS double"); }
;
}
Вывод:
drawing widget
drawing gadget
drawing widget
drawing widget
drawing gadget
x IS widget
x IS gadget
x IS widget
x IS widget
x IS gadget
y IS int
y IS double
y IS int
y IS int
y IS double