Поиск имён и разрешение перегрузок в классе с наследованием работает немного иначе, чем в глобальном контексте.
namespace foo { void f(int); voif f(float); }
namespace bar { void f(char); }
using namespace foo;
using namespace bar;
int main() { f(1); f('1'); }
Тут всё просто: берём все доступные пространства, выполняем ADL, фильтруем по совместимости аргументов.
А с членом класса такой номер не пройдёт
struct foo { void f(int); void f(float); };
struct bar { void f(char); };
struct buz : foo, bar {};
int main() { buz().f(1); }
Потому что сперва ищутся все доступные члены. И к найденным членам должен быть единственный путь в иерархии. Только после этого для функций выполняется поиск перегрузки.
(Там ещё есть нюансы с множественным наследованием одной и той же базы, и с виртуальным наследованием; но это уже детали).
Стандарт суров, но это стандарт!
Выход состоит в том, чтобы перетянуть нужные члены в класс-наследник
struct buz1 : foo, bar { using foo::f; };
struct buz2 : foo, bar { using foo::f; using bar::f; };
struct buz : foo, bar {};
struct buz_fail : buz0 { using buz0::f; }; // облом! поиск имени buz0::f привёл к неоднозначностиstruct xyz1 : buz1 {}; // можно спокойно наследоваться; здесь уже using f не нуженstruct xyz2 : buz2 {};
int main() {
xyz1().f('1'); // foo::f(int) предпочтительнее (float)
xyz2().f('1'); // bar::f(char) идеально подошёл
}
А теперь — задачка.
Пусть у нас есть много более-менее похожих баз, содержащих много одноимённх функций. Что-то в таком роде
template<class FT, class GT, class HT> struct base {
void f(FT);
void g(GT);
void h(HT);
};
struct A : base<int,char,long> {};
struct B .....;
struct C .....;
struct D .....;
Мы хотим собрать класс-наследник, объединяющий все базы и дающий доступ ко всем этим членам.
Как это сделать?
Варианты: препроцессорная магия; шаблонная магия.
Играя с препроцессорной магией, не забудьте, что базовый класс может иметь имя не в одну лексему (foo), а содержать всякие символы, на которые препроцессор отреагирует нервно: base<int,char,long> — запятые, например.
На шаблонной магии у меня решение есть.
Если кто предложит решение на boost::mpl, буду рад.
К>До С++11, без вариадиков, приходилось делать набор типов в кортеже — mpl::vector, например, — и свёртывать его операцией наследования.
Так тебе ведь нужно ещё вставить using'и, тут имхо как раз нужна свёртка с наследованием, где на каждом уровне будет свой using. По типу как в http://rsdn.ru/forum/cpp/4953155.flat.1
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Так тебе ведь нужно ещё вставить using'и, тут имхо как раз нужна свёртка с наследованием, где на каждом уровне будет свой using. По типу как в http://rsdn.ru/forum/cpp/4953155.flat.1
Конечно! Тут мы наталкиваемся на маленькое заподло, авторы стандарта забыли сделать развёртывание вариадиков в using (есть пропозал в 17)
template<class Base, class... Xs> struct using_f : Base { using Xs::f...; }; // так нельзя, но хочется
Поэтому приходится писать рекурсивную свёртку руками.
Только здесь будет не простая свёртка "унаследуй-и-объяви"; если у нас много членов, да ещё если мы хотим выборочно дёрнуть каждый из них (указать, для каких именно баз актуален тот или другой).
(Повторюсь: у меня есть готовое решение, — более того, я на нём тестирую компилятор).
Ну ты же сам просил MPL
inherit-linearly — это просто fold с соответствующей метафункцией.
В твоем случае метафункция — это однократное наследование плюс using внутри.
Если непонятно, как это сделать — скажи, я завтра нарисую (сейчас уже спать пора)
Здравствуйте, jazzer, Вы писали:
J>Ну ты же сам просил MPL J>inherit-linearly — это просто fold с соответствующей метафункцией. J>В твоем случае метафункция — это однократное наследование плюс using внутри. J>Если непонятно, как это сделать — скажи, я завтра нарисую (сейчас уже спать пора)
Вот в том и фокус, что using внутри получается богатый.
Как передружить все базы со всеми (а ещё лучше — выборочно) членами?
template<class A, class B> struct add_and_using_all : A, B
{
using A::f; using B::f;
using A::g; using B::g;
using A::h; using B::h;
... и так далее ...
};
Можно ли сконструировать add_and_using_all из somehow_using_f, somehow_using_g, и т.д.?
Отдельная — решаемая! — задача: пусть у нас уже есть готовый наследник, и мы хотим нахлобучить на него юзинг.
template<class Base, class Source> struct update_using_f : Base
{
using Base::f; // вот так нельзя, потому что Base::f неоднозначноusing Source::f; // мы хотели бы разрулить неоднозначность вот этой второй строчкой
};
Да, задачу можно привести к обычной свёртке, но свёртке не очень обычной функции
Как я понимаю, в бусте такими делами не заморачивались, штатного решения нет, — но, может быть, есть красивое решение.
А то мои опыты таковы, что или руками рекурсию писать, или делать обвязку для совместимости с boost::mpl соразмерную с ручной рекурсией.
Соответственно, f в using_f легким движением препроцессора превращается в using_fgh для трех функций вызовом макроса типа DECL_USING(using_fgh,(f)(g)(h)) c BOOST_PP_SEQ_xxx внутри.
Здравствуйте, jazzer, Вы писали:
J>Со всеми членами — понятия не имею. С выборочными — вот затравка:
Выборочно — я имею в виду, выдернуть член f не изо всех баз, а только из некоторых. Но унаследоваться при этом ото всех.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, jazzer, Вы писали:
J>>Со всеми членами — понятия не имею. С выборочными — вот затравка: К>Выборочно — я имею в виду, выдернуть член f не изо всех баз, а только из некоторых. Но унаследоваться при этом ото всех.
тогда проще всего их отсортировать на входе. Сначала впустую отнаследоваться от тех, кто не нужен, без using, а потом добавить тех, кто нужен, с using. Порядок же наследования не важен, я так предполагаю.
Здравствуйте, jazzer, Вы писали:
J>тогда проще всего их отсортировать на входе. Сначала впустую отнаследоваться от тех, кто не нужен, без using, а потом добавить тех, кто нужен, с using. Порядок же наследования не важен, я так предполагаю.
А что, если мы напишем-таки метафункцию...
#define DECLARE_USING(member) ??? // строительный кирпич - некий шаблон, делающий using Arg::member#define USING(member) ??? // имя кирпича#define USING_KIND ??? // вид этого кирпича - class, template<???>class и т.п.template<class Alloy, USING_KIND Using, class... Elements> using add_using_spec = ???
// эквивалентноstruct add_using_spec : Alloy
{
using Element1::member;
using Element2::member;
.....
};
// далее всё будет очень простоusing the_alloy = inherit<A,B,C,D>;
using the_alloy_with_f = add_using_spec<the_alloy, USING(f), A,B>;
using the_alloy_with_fg = add_using_spec<the_alloy_f, USING(g), C,D>;
using the_alloy_with_fgh = add_using_spec<the_alloy_fg, USING(h), A,B,C>;
На С++98, естественно, все метафункции придётся засунуть внутрь классов, результаты вынимать через зависимые типы. Ну и вариадики обернуть в кортежи. Но это уже мелкие неудобства.