Переадресация вызовов базового класса с вариативным шаблоном

В C ++ до 11 у меня было что-то вроде этого:

template<class T,class U,class V>
struct Foo : T,U,V {

  bool init() {

    if(!T::init() || !U::init() || !V::init())
      return false;

    // do local init and return true/false
  }
};

Я хотел бы преобразовать это в вариативный синтаксис C ++ 11, чтобы воспользоваться преимуществами списка аргументов гибкой длины. Я понимаю концепцию распаковки списка аргументов шаблона с использованием рекурсии, но я просто не могу понять, как правильно понять синтаксис. Вот что я пробовал:

template<typename... Features>
struct Foo : Features... {

  template<typename F,typename... G>
  bool recinit(F& arg,G&& ...args) {

    if(!F::init())
      return false;

    return recinit<F,G...>(args...);
  }

  bool init() {
    // how to call recinit() from here?
  }
};

Я бы предпочел, чтобы порядок вызовов функций init () базового класса был слева направо, но это не критично.


person Andy Brown    schedule 08.03.2013    source источник
comment
Не уверен, чего вы пытаетесь достичь: ваша recinit функция бесконечно рекурсивна, или я ошибаюсь? как вы рассчитываете вызвать его из init(), с какими аргументами?   -  person Andy Prowl    schedule 08.03.2013
comment
Прекратите использовать метод init и используйте конструктор.   -  person Nicol Bolas    schedule 08.03.2013


Ответы (3)


Это должно работать:

template<typename F, typename... T>
    struct recinit;

template<typename F>
    struct recinit<F> {
        static bool tinit(F *) {
            return true;
        }
    };
template<typename F, typename T, typename... G>
    struct recinit<F, T, G...> {
        static bool tinit(F *ptr)  {
            if (!ptr->T::init())
                return false;
            return recinit<F, G...>::tinit(ptr);
        }
    };

template<typename... Features>
struct Foo : Features... {

    bool init() {
        bool res = recinit<Foo, Features...>::tinit(this);
        //use res wisely
    }
};

Ваша проблема в том, что вы не можете писать частичные специализации функций, только классов / структур. И вспомогательная структура должна находиться за пределами Foo, иначе она получит аргументы шаблона из включающей структуры, и это было бы плохо.

Вы не говорите, но я предполагаю, что init - нестатическая функция-член. В таком случае args аргументы не имеют смысла: все они должны быть this! Так что просто пропустите это один раз и избегайте пачки аргументов. Я попытался передать this как void*, но это может вызвать проблемы, поэтому я просто добавил дополнительный аргумент шаблона в recinit, который будет Foo.

Кроме того, каждый раз, когда вы делаете один рекурсивный шаг, не забудьте удалить один параметр.

person rodrigo    schedule 08.03.2013
comment
Несмотря на то, что я уверен, что все проголосованные ответы хороши, это тот, который больше всего соответствует моим требованиям. Я подключил его к своей иерархии, и он отлично работает, и, родриго, вы были правы, предполагая, что init () является нестатической функцией-членом. Я узнал кое-что новое сегодня, да и в пятницу тоже. Хорошая вещь. - person Andy Brown; 08.03.2013
comment
@AndyBrown: Перечитывая мой ответ, static_cast на самом деле не нужен, потому что T должен быть базовым классом F. Убрал, код всегда красивее без приведения. - person rodrigo; 08.03.2013

Может быть, вы могли бы попробовать что-то вроде этого:

template<typename... Features>
struct Foo : Features...
{
    bool init()
    {
        // Courtesy of Xeo :-)
        auto il = {(static_cast<bool (Foo::*)()>(&Features::init))...};
        return std::all_of(il.begin(), il.end(), 
            [this] (bool (Foo::*f)()) { return (this->*f)(); }
            );
    }
};

Вот альтернативная, более подробная версия, в которой используются вариативные шаблоны:

template<typename... Features>
struct Foo : Features...
{
    bool init()
    {
        return combine_and((&Features::init)...);
    }

private:

    bool combine_and()
    {
        return true;
    }

    template<typename F>
    bool combine_and(F f)
    {
        return (this->*f)();
    }

    template<typename F1, typename... Fs>
    bool combine_and(F1 f1, Fs... fs)
    {
        return ((this->*f1)() && combine_and(fs...));
    }
};

Какое бы решение вы ни выбрали, вы можете использовать его следующим образом:

#include <iostream>

using namespace std;

struct A { bool init() { cout << "Hello " << endl; return true; } };
struct B { bool init() { cout << "Template " << endl; return true; } };
struct C { bool init() { cout << "World!" << endl; return true; } };

int main()
{
    Foo<A, B, C> f;
    bool res = f.init(); // Prints "Hello Template World!"
    cout << res; // Prints 1
}
person Andy Prowl    schedule 08.03.2013
comment
Однако при этом не собираются возвращаемые значения из init функций. Должно быть больше похоже на fold<collect_invoke_init, always_true_init, Features...>();. - person Xeo; 08.03.2013
comment
Поправьте меня, если я ошибаюсь, но я считаю, что код OP включает оценку короткого замыкания init функций, тогда как ваш код оценивает все init функции перед исследованием их результатов. Я не знаю, важно ли здесь различие, но потенциально оно может привести к разным результатам. - person Andrew Durward; 08.03.2013
comment
@Xeo: Отлично. Тем не менее, он по-прежнему должен вызывать все функции инициализации. Собираюсь опубликовать решение, которого нет. - person Andy Prowl; 08.03.2013
comment
@AndrewDurward: Верно. Я собираюсь опубликовать решение, которое использует короткое замыкание. - person Andy Prowl; 08.03.2013
comment
@Andrew Как вы думаете, почему у all_of нет короткого замыкания? Это было бы явно странной реализацией. - person Konrad Rudolph; 08.03.2013
comment
@KonradRudolph: all_of может быть закорочен, но вы передаете ему только список bool, который вы получаете при вызове всех init функций. - person Xeo; 08.03.2013
comment
@Xeo Э, ну, то же самое и в текущей реализации Энди. Очевидно, вам придется сконструировать initializer_list таким образом, чтобы значения еще не оценивались. / EDIT Ах, только что заметил отметку времени комментариев и последнего редактирования ответа. Я думал, что ответ Энди пришел после комментариев. - person Konrad Rudolph; 08.03.2013
comment
@KonradRudolph: Я опубликовал решение, которое использует короткое замыкание. Тот, который основан на initializer_list, предпочитал простоту. - person Andy Prowl; 08.03.2013
comment
@ Энди Да, извини; почему-то я подумал, что Эндрю имел в виду комментарий Зео. В текущем решении нет ничего плохого, но мне было бы очень интересно увидеть решение, которое закорочено, но при этом использует all_of. Очевидно, без накладных расходов на std::function. ;-) / EDIT Забудьте об этом, для этого требуются полиморфные лямбды. Черт. - person Konrad Rudolph; 08.03.2013
comment
Извините, последний комментарий. ваш код не работает, если Features... содержит только один тип. Якорь рекурсии для combine_and должен принимать единственный аргумент (или, альтернативно, нет). - person Konrad Rudolph; 08.03.2013
comment
@KonradRudolph: Хорошее наблюдение, я исправлю. И я пытаюсь найти решение, которое закорочено и использует std::all_of, но это не кажется простым :) - person Andy Prowl; 08.03.2013
comment
@KonradRudolph: Хорошо, давай - person Andy Prowl; 08.03.2013
comment
@Xeo: Можно ли использовать Base::* на Derived::*, как вы? - person Luc Touraille; 08.03.2013
comment
@Xeo: Блин, ты был быстрее :-) - person Andy Prowl; 08.03.2013
comment
@Andy Argh, снова кастинги в стиле Си. И две избыточные combine_and перегрузки. Хорошо, я сейчас пойду к двери, извините за спам в комментариях. :-p… Хорошее обсуждение. - person Konrad Rudolph; 08.03.2013
comment
@KonradRudolph: Да, я знаю, мне нужно прекратить с ними. Я редактировал. И уберет лишнюю перегрузку ;-) - person Andy Prowl; 08.03.2013
comment
@Xeo: Оказывается, это действительно нормально. - person Luc Touraille; 08.03.2013
comment
@LucTouraille: Да, функции-члены базового класса всегда будут в производном классе. Это может показаться немного странным, учитывая, что вы не можете просто преобразовать B* в D*, но T B::* в T D::*. Также обратите внимание, что обратное не работает. :) Нет T D::* T B::*. - person Xeo; 08.03.2013
comment
@Xeo, похоже, ваш пример кода не работает на Intel ICC v15. Он ошибается с смещениями для базовых классов. - person BlamKiwi; 25.03.2015

У вашего кода две проблемы. Во-первых, довольно обыденное:

return recinit<F,G...>(args...);

Вы уже работали с F, не включайте его в список аргументов.

return recinit<G...>(args...);

(Кроме того, вам, вероятно, следует точно пересылать аргументы.)

Во-вторых, код не будет компилироваться, потому что ваша рекурсия имеет привязку во время выполнения , но не во время компиляции. То есть компилятор попытается распаковать пакет аргументов G до бесконечности. Чтобы предотвратить это, вам необходимо специализировать функцию для пустого списка аргументов шаблона.

person Konrad Rudolph    schedule 08.03.2013