Как я могу расширить вызов базовых классов вариационных шаблонов?

У меня есть набор неортогональных политик, все из которых реализуют общий именованный метод, политики добавляют проверки безопасности. Я хочу, чтобы пользователи могли комбинировать политики, чтобы обеспечить более сложную проверку без создания политик для каждого случая комбинации вручную. Мой подход создает новый класс политики для объединения других.

В упрощенном примере ниже показан C как класс объединения, здесь объединен метод id. Ожидаемый результат - при вызове идентификатора на C для последовательного вызова идентификатора каждого базового класса.

#include <iostream>
using namespace std;

struct A 
{
    void id() { cout << "A ";}
};

struct B 
{
    void id() { cout << "B ";}
};

template<class A, class... As>
struct C : public A, public As... 
{
    void id()
    {
         A::id();
         As...::id(); // This line does not work, it is illustrative.
    }
};

int main()
{
    C<A, B> c;
    c.id();

    //expected: result A B 
}

Вопрос: возможно ли расширить As... как-то сделать это без использования рекурсивного подхода, просто используя оператор...?

Ответ 1

Конечно. Вам нужен контекст, который разрешает расширение пакета - простой - это скопированный список инициализаторов, который также имеет преимущество для оценки слева направо:

using expander = int[];
(void) expander { 0, ((void) As::id(), 0)... };
  • ... расширяет шаблон слева от него; в этом случае шаблон является выражением ((void) As::id(), 0).

  • , в выражении - это оператор запятой, который оценивает первый операнд, отбрасывает результат, затем оценивает второй операнд и возвращает результат.

  • As::id() существует As::id() для защиты от перегруженного operator, и может быть опущен, если вы уверены, что ни один из вызовов As::id() не вернет то, что перегружает оператор запятой.
  • 0 в правой части оператора запятой состоит в том, что expander является массивом int s, поэтому все выражение (которое используется для инициализации элемента массива) должно оцениваться как int.
  • Первый 0 гарантирует, что мы не пытаемся создать недопустимый массив 0-размера, если As - пустой пакет.

Демо.


В С++ 17 (если нам повезет), все тело C::id можно заменить на двоичное выражение сложения: (A::id(), ... , (void) As::id()); Demo.