Здравствуйте, Tilir, Вы писали:
T>Hi,
T>Возник теоретический спор об эквивалентности шаблонного и динамического полиморфизма в C++
T>Был предложен такой контрпример тезису об эквивалентности как виртуальный конструктор:
T>
T>class obj
T>{
T>public:
T> virtual int foo (int) = 0;
T>};
T>class A : public obj;
T>class B : public obj;
T>...
T>/* returns A is config have 'a', B is 'b', and so on */
T>obj * getfromconfig (const char *filename);
T>int
T>entry (obj *x)
T>{
T> return x->foo();
T>}
T>int
T>main (void)
T>{
T> return entry (getfromconfig("my.xml"))
T>}
T>
T>Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
T>--- T>With best regards, Konstantin
-1 за main(void) в С++
чтоб два раза не вставать — в коде нет реализации getfromconfig, которая содержит наиболее интересную часть кода.
Возник теоретический спор об эквивалентности шаблонного и динамического полиморфизма в C++
Был предложен такой контрпример тезису об эквивалентности как виртуальный конструктор:
class obj
{
public:
virtual int foo (int) = 0;
};
class A : public obj;
class B : public obj;
...
/* returns A is config have 'a', B is 'b', and so on */
obj * getfromconfig (const char *filename);
int
entry (obj *x)
{
return x->foo();
}
int
main (void)
{
return entry (getfromconfig("my.xml"))
}
Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
Здравствуйте, Кодт, Вы писали:
К>Ну вот хорошие макропроцессоры и развитые системы типов полны по Тьюрингу. К>Это значит, что мы можем зависнуть не только при запуске программы, но и при её компиляции. К>Не просто отожрать крендельон памяти, а честно зависнуть.
Здравствуйте, Tilir, Вы писали:
T>Возник теоретический спор об эквивалентности шаблонного и динамического полиморфизма в C++ T>Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
Шаблонный полиморфизм — статический. А статическому полиморфизму — статические параметры.
Я бы переписал так:
Здравствуйте, Tilir, Вы писали:
T>Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
Никак, если имя файла задаётся в рантайм.
Будет некая простыня. Пусть твой фабричный метод возвращает пару целочисленную переменную, задающую тип, и указатель (убыв бы) на сгенерированный тип, отнаследованный от базового. Тогда то, что ты хочешь выглядит примерно так.
(можно короче раза в два но не больше)
#include <cstdio>
#include <vector>
#include <functional>
using namespace std;
using namespace std::placeholders;
// семейство типов ABCDvoid show(char x) { printf("%c ", x); }
struct A { int foo() { show('A'); return 1; } } const a;
struct B { unsigned foo() { show('B'); return 2; } } const b;
struct C { long foo() { show('C'); return 3; } } const c;
struct D { double foo() { show('D'); return 4; } } const d;
// все наши полиморфные функции будут иметь первоклассный фиксированный типstruct getconfig_t // void g(char,K)
{
template<class K> // где K имеет сигнатуру void f(ABCD)void operator()(char t, K kont) const
{
switch(t)
{
case'a': kont(A()); break;
case'b': kont(B()); break;
case'c': kont(C()); break;
case'd': kont(D()); break;
default: printf("exception %c\n", t); break; // исключение состоит в том, чтобы не вызывать продолжение :)
}
}
} const getconfig;
struct entry_t // void e(ABCD,ABCD,K)
{
template<class T1, class T2, class K> // где T1 и T2 это ABCD, а K имеет сигнатуру void f(NUM)void operator()(T1 t1, T2 t2, K kont) const
{
kont(t1.foo()*10 + t2.foo()); // будем изучать правила продвижения числовых типов
}
} const entry;
struct finish_t // void f(NUM)
{
void operator()(int n) const { printf("int %d\n", n); }
void operator()(unsigned n) const { printf("uint %u\n", n); }
void operator()(long n) const { printf("long %ld\n", n); }
void operator()(unsigned long n) const { printf("ulong %lu\n", n); }
void operator()(double n) const { printf("double %g\n", n); }
} const finish;
// протектор, аналогичный boost::protect
// (его нет в стандартной библиотеке, поэтому пришлось стырить из интернета рецепт)template<typename T>
struct protect_wrapper : T
{
protect_wrapper(const T& t) : T(t)
{
}
protect_wrapper(T&& t) : T(std::move(t))
{
}
};
template<typename T>
typename std::enable_if< !std::is_bind_expression< typename std::decay<T>::type >::value,
T&& >::type
protect(T&& t)
{
return std::forward<T>(t);
}
// нам потребуется только эта веткаtemplate<typename T>
typename std::enable_if< std::is_bind_expression< typename std::decay<T>::type >::value,
protect_wrapper<typename std::decay<T>::type > >::type
protect(T&& t)
{
return protect_wrapper<typename std::decay<T>::type >(std::forward<T>(t));
}
// устойчивая идиома: изоляция bind-выраженияtemplate<class... Args>
auto probind(Args... args) -> decltype(protect(bind(args...))) { return protect(bind(args...)); }
// карринг двухместной функцииstruct curry_t
{
template<class F, class X>
auto operator()(F&& f, X&& x) const -> decltype( probind(f,x,_1) ) { return probind(f,x,_1); }
} const curry;
// комбинируем!void run(char x, char y)
{
// кирпичики: продолженияauto g = protect(bind(getconfig, x, _1)); // void(K)auto h = protect(bind(getconfig, y, _1)); // void(K)auto e = protect(bind(entry, _1, _2, _3)); // void(ABCD,ABCD,K)
// последний кирпичик - без продолженияauto f = finish;
// комбинацииauto fe = probind(e,_1,_2,f);
auto feh = probind(h, bind(curry,fe,_1));
auto fegh = probind(g, feh);
// запускаем
fegh();
}
int main()
{
vector<char> abcd = { 'a','b','c','d','e' };
for(auto x : abcd)
for(auto y : abcd)
run(x,y);
}
Карринг здесь нужен для того, чтобы разнести первый и второй аргументы замыкания fe на разные этажи bind-выражения.
Если просто написать probind(h, bind(fe,_1,_2)) — то получим двухместное замыкание, в котором h получает на вход конечный результат fe(_1,_2)
Если вместо bind сделать probind(h, probind(fe,_1,_2)) — то получим нульместное замыкание, в котором h получает двухместное.
А нам нужно одноместное и одноместное.
В стандартной библиотеке есть комбинатор bind1st, но, к сожалению, это шаблон функции.
Здравствуйте, Kernan, Вы писали:
K>Никак, если имя файла задаётся в рантайм.
Если хочется именно в компайлтайм, то что-то вроде этого. Но смысл? то, чего ты хочешь, ты не получишь.
struct obj
{
public:
virtual int foo () = 0;
};
struct A : public obj
{
virtual int foo () {std::cout<<"FOO::A"<<std::endl; return 0;}
};
struct B : public obj
{
virtual int foo () {std::cout<<"FOO::B"<<std::endl; return 0;}
};
template<typename T>
struct entryByFile: public T
{
int entry()
{
return T::make()->foo();
}
};
struct fileA
{
obj* make() {return new A;}
};
struct fileB
{
obj* make() {return new B;}
};
int
main (void)
{
return entryByFile<fileB>().entry();
}
Здравствуйте, Tilir, Вы писали: T>Хочется вообще избавиться от виртуальных функций, в чистом виде заменив рантайм-полиморфизм шаблонным.
Может я чего не так понял, но предположу вариант:
Скрытый текст
#include <iostream>
template<typename Tnext>
class Converter
{
public:
template<typename Tthis>
inline static Tnext* cast(Tthis* _this)
{
return static_cast<Tnext*>(_this);
}
};
template<typename Tfist, typename Tsecond>
class ChainConverter
{
public:
template<typename Tthis>
inline static auto cast(Tthis* _this)-> decltype (Tsecond::cast(Tfist::cast(_this)))
{
return Tsecond::cast(Tfist::cast(_this));
}
};
template<typename TConverter>
class obj
{
public:
typedef obj<TConverter> TObj;
public:
// предположим:
/*virtual*/int foo() /* = 0;*/
{ return TConverter::cast(this)->foo(); }
// чтобы сделать виртуальный вызов из наследников без приведенияint virtualFoo(){ return foo(); }
protected:
obj(){}
};
template<typename TConverter>
class Aimpl : public obj<ChainConverter<Converter<Aimpl<TConverter>>,TConverter>>
{
public:
typedef Aimpl<TConverter> TAimpl;
public:
int foo() {std::cout << "FOO::A" << std::endl; return 0; }
};
template<typename TConverter>
class Bimpl : public Aimpl<ChainConverter<Converter<Bimpl<TConverter>>, TConverter>>
{
public:
typedef Bimpl<TConverter> TBimpl;
public:
int foo() { std::cout << "FOO::B" << std::endl; return 0; }
};
template<typename TConverter>
class Cimpl : public Aimpl<ChainConverter<Converter<Cimpl<TConverter>>, TConverter>>
{
public:
typedef Cimpl<TConverter> TCimpl;
public:
int foo() { std::cout << "FOO::C" << std::endl; return 0; }
};
template<typename TConverter>
class Dimpl :
public Bimpl<ChainConverter<Converter<Dimpl<TConverter>>, TConverter>>,
public Cimpl<ChainConverter<Converter<Dimpl<TConverter>>, TConverter>>
{
public:
typedef Dimpl<TConverter> TDimpl;
public:
int foo() { std::cout << "FOO::D" << std::endl; return 0; }
};
class A : public Aimpl<Converter<A>>
{
};
class B : public Bimpl<Converter<B>>
{
};
class C : public Cimpl<Converter<C>>
{
};
class D : public Dimpl<Converter<D>>
{
};
int main()
{
A a;
a.foo();
a.virtualFoo();
B b;
B::TObj& ob = b;
B::TAimpl& aimpl = b;
ob.foo();
ob.virtualFoo();
b.foo();
b.virtualFoo();
aimpl.foo();
aimpl.virtualFoo();
std::cout << "--D--" << std::endl;
D d;
d.foo();
d.TBimpl::foo();
d.TBimpl::TAimpl::foo();
d.TCimpl::foo();
d.TCimpl::TAimpl::foo();
d.TBimpl::TObj::virtualFoo();
d.TCimpl::TObj::virtualFoo();
return 0;
}
/* Output:
FOO::A
FOO::A
FOO::B
FOO::B
FOO::B
FOO::B
FOO::A
FOO::B
--D--
FOO::D
FOO::B
FOO::A
FOO::C
FOO::A
FOO::D
FOO::D
// */
.
Если я его правильно понял, то он предложил перенести точку входа в программу во внутреннюю лямбду, где конфигурация уже определена.
Тогда не код будет управлять данными, а под данные будет подстраиваться код...
Если немного переделать, то получится примерно так:
Скрытый текст
#include <iostream>
#include <string>
#include <type_traits>
class A
{
public:
int foo() { std::cout << "FOO::A" << std::endl; return 0; }
};
class B
{
public:
int foo() { std::cout << "FOO::B" << std::endl; return 0; }
};
class C
{
public:
int foo() { std::cout << "FOO::C" << std::endl; return 0; }
};
class D
{
public:
int foo() { std::cout << "FOO::D" << std::endl; return 0; }
};
template<int AppRestartRequestCount>
class EmptyConfig
{
public:
static const int typesRemainsCount = 4;
static const int appRestartRequestCount = AppRestartRequestCount;
};
template<typename TParentConfig, typename T, typename Q = T>
class Config :public TParentConfig
{
public:
typedef T TType;
typedef TParentConfig TParent;
public:
static const int typesRemainsCount = TParentConfig::typesRemainsCount - 1;
};
template<typename TParentConfig, typename T>
class Config<TParentConfig, T, typename std::enable_if<TParentConfig::typesRemainsCount == 0, T>::type>
{
public:
typedef T TType;
typedef TParentConfig TParent;
public:
static const int typesRemainsCount = -1;
};
template<typename TLayeredConfig>
class FullConfig
{
public:
typedef typename TLayeredConfig::TType T4;
typedef typename TLayeredConfig::TParent::TType T3;
typedef typename TLayeredConfig::TParent::TParent::TType T2;
typedef typename TLayeredConfig::TParent::TParent::TParent::TType T1;
public:
static const int typesPackCount = EmptyConfig<0>::typesRemainsCount;
static const int appRestartRequestCount = TLayeredConfig::appRestartRequestCount;
};
template<typename TConfig>
class ReverseConfig
{
public:
typedef typename TConfig::T1 T4;
typedef typename TConfig::T2 T3;
typedef typename TConfig::T3 T2;
typedef typename TConfig::T4 T1;
static const int typesPackCount = TConfig::typesPackCount;
static const int appRestartRequestCount = TConfig::appRestartRequestCount - 1;
};
template<typename TConf>
class AppStop
{
public:
static int run()
{
return -1;
}
};
template<typename TConf>
class App
{
public:
static int run()
{
typename TConf::T1 t1;
typename TConf::T2 t2;
typename TConf::T3 t3;
typename TConf::T4 t4;
t1.foo();
t2.foo();
t3.foo();
t4.foo();
std::cout << "reverse reconfig.." << std::endl;
return std::conditional<TConf::appRestartRequestCount != 0, App<ReverseConfig<TConf>>, AppStop<TConf>>::type::run();
}
};
template<typename TConfig, template <typename> class TApp, typename Q = TConfig>
class Configurator
{
public:
static int configAndRun(const std::string& /*_s*/)
{
return TApp<FullConfig<TConfig>>::run();
}
};
template<typename TConfig, template <typename> class TApp>
class Configurator<TConfig, TApp,
typename std::enable_if<TConfig::typesRemainsCount != 0, TConfig>::type>
{
public:
static int configAndRun(const std::string& _s)
{
auto c = _s.at(0);
auto nextS = _s.substr(1);
switch (c)
{
default:
case'A':
return Configurator<Config<TConfig, A>, TApp>::configAndRun(nextS);
case'B':
return Configurator<Config<TConfig, B>, TApp>::configAndRun(nextS);
case'C':
return Configurator<Config<TConfig, C>, TApp>::configAndRun(nextS);
case'D':
return Configurator<Config<TConfig, D>, TApp>::configAndRun(nextS);
}
}
};
int main()
{
const char* configs[] = { "ABCD", "DABC" };
for (auto& cfg : configs)
{
std::cout << "main config: " << cfg << std::endl;
Configurator<EmptyConfig<1>, App>::configAndRun(cfg);
}
return 0;
}
/* Output:
main config: ABCD
FOO::A
FOO::B
FOO::C
FOO::D
reverse reconfig..
FOO::D
FOO::C
FOO::B
FOO::A
reverse reconfig..
main config: DABC
FOO::D
FOO::A
FOO::B
FOO::C
reverse reconfig..
FOO::C
FOO::B
FOO::A
FOO::D
reverse reconfig..
*/
Здравствуйте, Tilir, Вы писали:
Не очень понял, причём тут конструктор, но
class PredicateExample {
public:
static int Do(int param) {return 0;}
};
class BaseObj {
public:
typedef int (*Function)(int);
BaseObj(Function fu) : foo_address(fu) {}
int foo(int param) {return foo_address(param);}
protected:
void set_foo_address(Function fu) {foo_address = fu;}
private:
Function foo_address;
};
template <class Predicate>
class Obj : public BaseObj
{
public:
Obj() : BaseObj(Predicate::Do) {}
};
class APredicate;
class BPredicate;
class A : public Obj<APredicate> {};
class B : public Obj<BPredicate> {};
//...
/* returns A is config have 'a', B is 'b', and so on */
BaseObj * getfromconfig (const char *filename);
int entry (BaseObj *x)
{
return x->foo(3);
}
int main (void)
{
return entry (getfromconfig("my.xml"))
}
Мораль тут в том, что виртуальность — это такой хитрожопый способ задать сишный указатель на функцию, поэтому с помощью указателя на функцию его всегда легко можно сымитировать. А уж чем имитировать — статическим полиморфизмом или просто на си фигарить тоже самое, второй вопрос. Можно даже на статическом полиморфизме наваять класс, который будет имитировать таблицу виртуальных функций. Только зачем?
Здравствуйте, Molchalnik, Вы писали:
К>>Если мы исходим из того, что машина, на которой будет исполняться динамика, — конечная, то можно перечислить все её состояния и захардкодить граф переходов. К>>Как честно захардкодить бесконечный автомат?
M>Если возможности "статики" полны по тьюрингу, то без проблем. Просто компиляция будет очень-очень долгой и памяти должно быть достаточно для данной задачи.
Ну вот хорошие макропроцессоры и развитые системы типов полны по Тьюрингу.
Это значит, что мы можем зависнуть не только при запуске программы, но и при её компиляции.
Не просто отожрать крендельон памяти, а честно зависнуть.
Здравствуйте, Abyx, Вы писали:
A>чтоб два раза не вставать — в коде нет реализации getfromconfig, которая содержит наиболее интересную часть кода.
Я же написал коммент. Очевидный фабричный метод:
/* returns A if config have 'a', B is 'b', and so on */
obj *
getfromconfig (const char *filename)
{
fstream f (filename, ios_base::in);
if (existsin (f, 'a'))
return new A();
if (existsin (f, 'b'))
return new B();
...
}
Здравствуйте, Kernan, Вы писали:
K>>Никак, если имя файла задаётся в рантайм. K>Если хочется именно в компайлтайм, то что-то вроде этого. Но смысл? то, чего ты хочешь, ты не получишь.
struct obj
{
public:
virtual int foo () = 0;
};
Хочется вообще избавиться от виртуальных функций, в чистом виде заменив рантайм-полиморфизм шаблонным. В этом варианте она все таки осталась.
Здравствуйте, Tilir, Вы писали:
T>Хочется вообще избавиться от виртуальных функций, в чистом виде заменив рантайм-полиморфизм шаблонным. В этом варианте она все таки осталась.
Тогда так. Но в рантайме это будет работать одинаково т.к. параметризовать можно только до компиляции кода. Т.е. ты не получаешь полиморфного поведения в рантайме.
template<typename T>
struct entryByFile: public T
{
int entry()
{
return T::foo();
}
};
struct fileA
{
int foo() {std::cout<<"DO something"<<std::endl; return 0;}
};
struct fileB
{
int foo() {std::cout<<"DO nothing"<<std::endl; return 0;}
};
int main()
{
return entryByFile<fileB>().foo();
}
T>class obj
T>{
T>public:
T> virtual int foo (int) = 0;
T>};
T>class A : public obj;
T>class B : public obj;
T>...
T>/* returns A if config have 'a', B is 'b', and so on */
T>obj *
T>getfromconfig (const char *filename)
T>{
T> fstream f (filename, ios_base::in);
T> if (existsin (f, 'a'))
T> return new A();
T> if (existsin (f, 'b'))
T> return new B();
T> ...
T>}
T>int
T>entry (obj *x)
T>{
T> return x->foo(); // тут, кситати, не верно метод вызываешь, или неверно описал я :xz:
T>}
T>int
T>main (void)
T>{
T> return entry (getfromconfig("my.xml"))
T>}
T>
T>Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
template<typename TObj> int entry( TObj* obj ) { return obj->foo(); }
int
main (void)
{
const char *filename = "my.xml";
fstream f (filename, ios_base::in);
if (existsin (f, 'a'))
return entry( new A );
if (existsin (f, 'b'))
return entry( new B );
...
}
Но смысл?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, denisko, Вы писали:
D>Будет некая простыня. Пусть твой фабричный метод возвращает пару целочисленную переменную, задающую тип, и указатель (убыв бы) на сгенерированный тип, отнаследованный от базового. Тогда то, что ты хочешь выглядит примерно так.
Очень круто, я серьёзно. К сожалению при всей методологической полезности, организация RTTI руками в этом случае как раз и доказывает необходимость именно динамического полиморфизма.
Здравствуйте, F3V, Вы писали:
F3V>Может я чего не так понял, но предположу вариант:
Тоже хороший вариант. Известна двойственность виртуальных функций и PImpl. Но по сути это такое же "RTTI своими силами", как и в случае http://rsdn.ru/forum/cpp/5728786.1
Такой подход скорее доказывает что динамический полиморфизм не сводится к шаблонному, раз уже нам пришлось по факту его воспроизводить, заводя ту же vtbl но руками.
int res;
getfromconfig("my.xml", [&](auto my) {
getfromconfig("his.xml", [&](auto his) {
res = entry(my,his);
})
});
К>>А чтоб удобнее пользоваться было, а не громоздить руками — придётся прикручивать карринг и комбинаторную логику.
T>Было бы очень интересно посмотреть хотя бы набросок.
Чуть попозже попробую формально подойти к этому.
Кажется, что std::bind на такое, в принципе, способен.
Здравствуйте, Tilir, Вы писали:
T>Это читерство.
Это не читерство, а статический полиморфизм так работает...
T>Идея контрпримера была в том, что getfromconfig используется не только в entry, а где угодно.
Ну так и пишешь это всё "где угодно" шаблонным, потом параметризуешь так и сяк, а вместо конфига можно просо нужную версию ставить, например, или в мэйне выбирать...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
К>// карринг двухместной функции
К>struct curry_t
К>{
К> template<class F, class X>
К> auto operator()(F&& f, X&& x) const -> decltype( probind(f,x,_1) ) { return probind(f,x,_1); }
К>} const curry;
// более универсальное решение - расслаивание аргументов на немедленно подставленные и идущие в составе синтаксического дерева bindstruct _1_t {} const _1_; auto _pass_(_1_t) -> decltype(_1) { return _1; }
struct _2_t {} const _2_; auto _pass_(_2_t) -> decltype(_2) { return _2; }
struct _3_t {} const _3_; auto _pass_(_3_t) -> decltype(_3) { return _3; }
template<class T> auto _pass_(T&& t) -> T&& { return t; }
struct partial_t
{
template<class F>
auto operator()(F&& f) const -> decltype( probind(f) )
{ return probind(f); }
template<class F, class T1>
auto operator()(F&& f, T1&& t1) const -> decltype( probind(f, _pass_(t1)) )
{ return probind(f, _pass_(t1)); }
template<class F, class T1, class T2>
auto operator()(F&& f, T1&& t1, T2&& t2) const -> decltype( probind(f, _pass_(t1), _pass_(t2)) )
{ return probind(f, _pass_(t1), _pass_(t2)); }
} const partial;
К>// комбинируем!
К>void run(char x, char y)
К>{
К> // кирпичики: продолжения
К> auto g = protect(bind(getconfig, x, _1)); // void(K)
К> auto h = protect(bind(getconfig, y, _1)); // void(K)
К> auto e = protect(bind(entry, _1, _2, _3)); // void(ABCD,ABCD,K)
К> // последний кирпичик - без продолжения
К> auto f = finish;
К> // комбинации
К> auto fe = probind(e,_1,_2,f);
auto feh = probind(h, bind(partial,fe, _1_, _1));
К> auto fegh = probind(g, feh);
К> // запускаем
К> fegh();
К>}
К>
Здесь _1 является формальным аргументом замыкания feh и будет подставлен при вызове его из h
а _1_ — аргументом того замыкания, которое будет возвращено функцией partial.
А что делать, если getfromconfig создаёт объект, а использовать его надо через какое-то время?
Сейчас, насколько я понял, мы не теряем информацию о типе создаваемого getfromconfig объекта.
Здравствуйте, tdiff, Вы писали:
T>А что делать, если getfromconfig создаёт объект, а использовать его надо через какое-то время? T>Сейчас, насколько я понял, мы не теряем информацию о типе создаваемого getfromconfig объекта.
Очевидно, что любая ленивость и любое откладывание на неопределённое "потом" переводит полиморфизм в рантайм.
Единственный вариант чисто статического полиморфизма — это весь main() пересадить на рельсы CPS. В принципе, это возможно.
Компромиссное решение — это сделать некоторое статически параметризованное окружение, т.е. шаблон класса, например, — у которого будет виртуальная точка входа.
// былоclass Environment
{
Base* x;
Base* y;
void one(char cx) { x = getconfig(cx); }
void two(char cy) { y = getconfig(cy); }
void three()
{
..... здесь гора всякого полезного кода .....
}
.....
};
// сталоstruct IEnvironment
{
IEnvironment* one(char cx);
virtual IEnvironment* two(char cy) = 0;
virtual IEnvironment* three() = 0;
};
template<class X, class Y> // чтобы компилировалось, нужно начать с каких-то null-объектов - заглушек (например)class Environment : public IEnvironment
{
X x;
Y y;
virtual IEnvironment* one(char cx) override
{
IEnvironment* res = nullptr;
getconfig(cx, [&](auto x) { res = new Environment<decltype(x),Y>(); res->x = x; });
return res;
}
virtual IEnvironment* two(char cy) override
{
IEnvironment* res = nullptr;
getconfig(cy, [&](auto y) { res = new Environment<X,decltype(y)>(); res->y = y; });
return res;
}
virtual void three() override
{
..... код, конкретизированный для X,Y .....
}
.....
};
И завернуть всё это хозяйство в идиому pimpl и паттерн State.
Здравствуйте, Molchalnik, Вы писали:
M>Не очень понял, причём тут конструктор, но
Это ещё одна прекрасная реализация vtbl руками, которых набралось уже много в этом треде. Она скорее доказывает, что динамический полиморфизм существенно необходим, раз уж приходится его реализовывать хакерскими способами при попытке обойтись шаблонами.
Впрочем, решение уважаемого Кодт'а (пожалуй лучшее в этом треде) показывает забавный способ вывернуть абстракцию.
Здравствуйте, Tilir, Вы писали:
T>Здравствуйте, Molchalnik, Вы писали:
M>>Не очень понял, причём тут конструктор, но
T>Это ещё одна прекрасная реализация vtbl руками, которых набралось уже много в этом треде. Она скорее доказывает, что динамический полиморфизм существенно необходим, раз уж приходится его реализовывать хакерскими способами при попытке обойтись шаблонами.
Дружище, давай не будем мешать тёплое с мягким. В твоём мире может быть всё, что угодно, ты в нём хозяин, но если речь идёт об объективной действительности, то нужно руководствоваться законами этой действительности, в частности, логикой. Ты дал два противоречивых запроса — "как сделать ЭТО без виртуальных функций" и "Проверить эквивалентность динамического и статического полиморфизма". Эти две вещи никак не связаны . Что с того, если этот пример нельзя реализовать без vtbl? Это не означает "неэквивалентности", ты исходишь из ложной посылки, имеешь мусор на входе, получишь мусор на выходе. К тому же, если следовать твоей логике, то 90% использований указателей на функции будут "попытками имитации vtbl". Не кажется ли тебе, что это бред?
Второе, динамический полиморфизм — это принцип программирования, а не фича языка, а не важно, на чём и как его делать, хоть на ассемблере.
Эквивалентнось полиморфизмов означает, что любой запрос юзера или любой алгоритм может быть реализован первым или вторым способом. Проблема твоего фрейма (=твоего восприятия реальности) в том, что ты под эквивалентностью понимаешь возможность не повторения алгоритма, а повторения метода реализации этого алгоритма. Кто-то интуитивно поймал эту идею и попытался тебе объяснить, но не очень удачно.
Если формализовать твой запрос (именно тот, который выражен через пример), перевести его на язык логики, то получится, "можно ли реализовать динамический полиморфизм без динамического полиморфизма". И получаешь соответственные ответы. Но если ответ НЕТ , то это не означает неэквивалентности, просто означает то, что динамический полиморфизм нельзя реализовать без динамического полиморфизма. Впрочем, насчёт последнего не уверен, ещё подумаю.
Здравствуйте, Molchalnik, Вы писали:
M> руководствоваться законами этой действительности, в частности, логикой
Не понял сути предъяв за логику и перехода на личности. Есть спорный тезис, что статический полиморфизм (на шаблонах) эквивалентен динамическому (на виртуальных функциях).
Разумеется и тот и другой полиморфизмы могут быть фейкнуты в C стиле -- я могу сделать внешний генератор вместо шаблонов или реализовать vtbl руками. Точно так же я вообще могу написать любой код на ассемблере и что?
Просто, если без подтасовок не обойтись, значит этот тезис для C++ неверен, всего-то. Скажем легко проиллюстрировать что указатели на C++ мощнее ссылок -- достаточно написать на них стек. А вот со статическим/динамическим полиморфизмом даже виртуальный конструктор оказывается недостаточно убедителен как пример.
И главное, я ведь не защищаю и не опровергаю этот тезис, а лишь ищу (и нахожу, как показывает тред) на форуме интересные идеи на тему. Ваше участие я оценил (см. оценку на первом посте). Если по делу больше сказать нечего, я готов признать себя нелогичным и т.п. Мы тут не в КСВ, так что я предлагаю обсуждать по существу.
Здравствуйте, Molchalnik, Вы писали:
M>Эквивалентнось полиморфизмов означает, что любой запрос юзера или любой алгоритм может быть реализован первым или вторым способом. Проблема твоего фрейма (=твоего восприятия реальности) в том, что ты под эквивалентностью понимаешь возможность не повторения алгоритма, а повторения метода реализации этого алгоритма. Кто-то интуитивно поймал эту идею и попытался тебе объяснить, но не очень удачно.
Поскольку статический полиморфизм должен отработать вширь на стадии компиляции, — почти любая задача, интенсивно эксплуатирующая множество полиморфных объектов, взорвёт компиляцию.
Однако, есть задачи, которые можно переделать в статику. Разбор конфигов — одна из таких.
Кстати, с конфигами это не шутки. У меня нечто похожее:
template<
class AcousticModelType, // 2 типа акустических моделей, - грузятся из файла (выбранного в конфиге)class GraphType, // 3 формата графов переходов, - грузятся из бинарного файла (выбранного в конфиге)class OutputLatticeType // 2 типа выходной сетки гипотез, - выбираются в конфиге
>
class SpeechDecoderImpl { ..... };
Поскольку это адская числодробилка, которая должна быстро запускаться и быстро вертеться, — то всё, что можно, избавлено от виртуальных вызовов и даже от лишних указателей.
Здравствуйте, Tilir, Вы писали:
T>Здравствуйте, Molchalnik, Вы писали:
M>> руководствоваться законами этой действительности, в частности, логикой
T>Не понял сути предъяв за логику и перехода на личности.
Перехода на личности не было.
Был логический разбор твоего примера с точки зрения провозглашённой тобой цели
T> Есть спорный тезис, что статический полиморфизм (на шаблонах) эквивалентен динамическому (на виртуальных функциях).
Эквивалентен в общем случае. И что? Как это опровергает твой пример? Мой тезис — никак. Считаю, и обосновал, что твой пример не связан с этим тезисом. Не подтверждает и не опровергает его.
T>Просто, если без подтасовок не обойтись, значит этот тезис для C++ неверен, всего-то.
Не согласен. Есть некая подмена понятий. "Можно ли любой динамический полиморфный код заменить на статический?" — ответ нет, и твой пример это иллюстрирует хорошо. "Эквивалентны ли динамический и статический полиморфизм?" — Ответ да, с точностью до константы.
Можно спорить о границах этой эквивалентности. Но отрицать её — это всё равно что при кодеревью идеального кода усмотреть один пробел в конце одной строки и за это поставить двойку. Необоснованный формализм.
Придраться к этому тезису можно, а опровергнуть дельно — нет.
Так же у тебя, по моему мнению, отсутствует понимание того, что замена одного метода на другой должна вызывать смену подхода. Т.е. замена динамического полиморфизма статическим — это не десяток мест в коде переписать. Это весь код выкинуть на свалку и написать взамен него другой, с другой архитектурой. Имхо, эквивалентность — это когда с помощью одного подхода построить любой алгоритм, который решает другой подход. Ты же считаешь, что эквивалентность — это когда любую строчку кода на динамическом полиморфизме можно заменить другой строчкой кода на статическом полиморфизме. А это, имхо, искажение понятия эквивалентности.
T>И главное, я ведь не защищаю и не опровергаю этот тезис, а лишь ищу (и нахожу, как показывает тред) на форуме интересные идеи на тему. Ваше участие я оценил (см. оценку на первом посте). Если по делу больше сказать нечего, я готов признать себя нелогичным и т.п. Мы тут не в КСВ, так что я предлагаю обсуждать по существу.
Здравствуйте, Tilir, Вы писали:
T>Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
T>
T>/* returns A is config have 'a', B is 'b', and so on */
T>obj * getfromconfig (const char *filename);
T>
T>int
T>entry (obj *x)
T>{
T> return x->foo();
T>}
T>
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Molchalnik, Вы писали:
К>Поскольку статический полиморфизм должен отработать вширь на стадии компиляции, — почти любая задача, интенсивно эксплуатирующая множество полиморфных объектов, взорвёт компиляцию.
Но ведь это проблемы компилятора и ресурсов компилирующей машины, а не тезиса об эквивалентности, верно?
Любая задача, решаемая в статике, может быть решена и в динамике. Просто бремя расчётов будет перенесено с real-time исполнения на процесс компиляции. Именно на этом базируется тезис (правильнее сказать, доказанная теорема) об эквивалентности статического и динамического полиморфизма.
Я бы сформулировал проблему статики иначе. Обратите внимание, все попытки опровергнуть тезис об эквивалентности построены на внешних данных, неизвестных на этапе компиляции. Других способов построить "опровержение" тезиса об эквивалентности нет и быть не может.
Так вот, с точки зрения математики, любой алгоритм, переводящий одни данные в другие может быть решён как в статике, так и в динамике. И разницы в том, как он будет решён, нет никакой — просто часть расчётов переносится на этап компиляции и код пишется под это. Более того, грамотная статика по определению эффективнее динамики (ибо часть расчётов производится на этапе компиляции, а так же происходит оптимизация общего кода под частный случай). Поэтому если стать на позицию своих оппонентнов, противников эквивалентности, то я бы признал то, что опровергнуть невозможно — да, математическая эквивалентность дескать есть, но нам, программистам, с этого нет никакого навара, потому что задачи, привязанные ко времени, статический полиморфизм не решит. С математической точки зрения это неправда, т.к. можно ввести время как один из параметров. А на практике — верно, т.к. машина не может предсказать все варианты ввода данных от пользователя и внешних устройств, и в зависимости от них построить код "на все случаи жизни" и рассчитать вывод для каждой секунды времени с момента запуска. Просто утонет в количестве комбинаций. Но теоретически — это возможно, поэтому теоретически статика и динамика абсолютно эквивалентны, а на практике — у каждой свои преимущества. Там, где статика применима при прямых руках она сделает динамику по скорости, иногда в несколько раз.
Здравствуйте, Molchalnik, Вы писали:
К>>Поскольку статический полиморфизм должен отработать вширь на стадии компиляции, — почти любая задача, интенсивно эксплуатирующая множество полиморфных объектов, взорвёт компиляцию. M>Но ведь это проблемы компилятора и ресурсов компилирующей машины, а не тезиса об эквивалентности, верно?
Теоретически, между теорией и практикой нет разницы. Практически, разница есть.
M>Любая задача, решаемая в статике, может быть решена и в динамике. Просто бремя расчётов будет перенесено с real-time исполнения на процесс компиляции. Именно на этом базируется тезис (правильнее сказать, доказанная теорема) об эквивалентности статического и динамического полиморфизма.
Статику в динамику перевести — дело нехитрое. Динамику в статику — это другое дело.
Если мы исходим из того, что машина, на которой будет исполняться динамика, — конечная, то можно перечислить все её состояния и захардкодить граф переходов.
Как честно захардкодить бесконечный автомат?
Причём речь даже не о полноценном вход-выходовом преобразователе, где на вход поступает потенциально бесконечная цепочка.
Есть ещё и такая фигня, как проблема останова.
Пусть программа на вход получает конечное множество данных и выдаёт ответ в конечной области значений, дополненной значением "ой-зациклилось".
Множество таких функций, очевидно, тоже конечное: M^N, где N — мощность входов, M — мощность выходов (включая ой).
Беда в том, что любую функцию можно описать неограниченным количеством способов.
Если компилятору не дать подсказку, где лежат грабли, то мы рискуем никогда не построить табличное представление функции.
Внешних состояний всего N, а внутренних — бесконечность.
Здравствуйте, Tilir, Вы писали: T>Hi, T>Возник теоретический спор об эквивалентности шаблонного и динамического полиморфизма в C++ T>Был предложен такой контрпример тезису об эквивалентности как виртуальный конструктор:
Скрытый текст
T>
T>class obj
T>{
T>public:
T> virtual int foo (int) = 0;
T>};
T>class A : public obj;
T>class B : public obj;
T>...
T>/* returns A is config have 'a', B is 'b', and so on */
T>obj * getfromconfig (const char *filename);
T>int
T>entry (obj *x)
T>{
T> return x->foo();
T>}
T>int
T>main (void)
T>{
T> return entry (getfromconfig("my.xml"))
T>}
T>
T>Как бы вы переписали этот код с шаблонным полиморфизмом вместо виртуальных функций?
несмотря на некорректность этого куска кода как контрпримера, переписать его можно просто, если не заморачиваться на копирование действий программы, а просто реализовать нужный алгоритм:
//class obj { public: virtual int foo (int) = 0; };class A {//: public obj {public:
int foo() {return (int)'a';}
};
class B {//: public obj {public:
int foo() {return (int)'b';}
};
template <class Object>
class Config1 {
public:
Object GetFrom (const char *filename) {return Object();}
};
template <class Object>
class Config2 {
public:
Object GetFrom (const char *filename) {return Object();}
};
template <template<typename> class Config, class Object>
int entry(const char * filename, Config<Object> & config) {
return config.GetFrom(filename).foo();
}
int main() {
Config1<A> config;
return entry("my.xml",config);
}
Предупреждая возражения "так в результате разбора конфига создаётся полиморфный объект, и он может быть абсолютно различным".
Да. В исходном примере создаётся. Но ведь задача избавится от динамического полиморфизма? Значит, в результате разбора конфига должен создаваться только один, неполиморфный объект, с функционалом достаточным, чтобы включать все возможные результаты разбора конфига. Это всегда возможно.
Кстати, если не ограничиватся обычным С++, то можно (легко) на 11х плюсах реализовать подлейший приём, который полностью повторит функционал динамического полиморфизма, однако, назвать его "имитацией динамики" не выйдет Но это попозже, сейчас много работы.
Здравствуйте, Кодт, Вы писали:
К>Статику в динамику перевести — дело нехитрое. Динамику в статику — это другое дело. К>Если мы исходим из того, что машина, на которой будет исполняться динамика, — конечная, то можно перечислить все её состояния и захардкодить граф переходов. К>Как честно захардкодить бесконечный автомат?
Если возможности "статики" полны по тьюрингу, то без проблем. Просто компиляция будет очень-очень долгой и памяти должно быть достаточно для данной задачи.