Ответ 1
Типичным подходом здесь является использование немого списка-инициализатора и расширение внутри него:
{ print(Args)... }
Порядок оценки гарантированно слева направо в фигурных инициаторах.
Но print
возвращает void
, поэтому нам нужно обойти это. Позвольте сделать его int тогда.
{ (print(Args), 0)... }
Это не будет работать как инструкция напрямую. Нам нужно указать тип.
using expand_type = int[];
expand_type{ (print(Args), 0)... };
Это работает до тех пор, пока в пакете Args
всегда есть один элемент. Матрицы нулевого размера недействительны, но мы можем обойти это, сделав его всегда имеющим хотя бы один элемент.
expand_type{ 0, (print(Args), 0)... };
Мы можем сделать этот шаблон многоразовым с помощью макроса.
namespace so {
using expand_type = int[];
}
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... }
// usage
SO_EXPAND_SIDE_EFFECTS(print(Args));
Однако для этого многократного использования требуется немного больше внимания к некоторым деталям. Мы не хотим использовать здесь перегруженные запятые. Запятая не может быть перегружена одним из аргументов void
, поэтому давайте воспользоваться этим.
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
::so::expand_type{ 0, ((PATTERN), void(), 0)... }
Если вы paranoid боитесь, что компилятор не назначает большие массивы нулей, вы можете использовать какой-то другой тип, который может быть инициализирован таким списком, но ничего не сохраняет.
namespace so {
struct expand_type {
template <typename... T>
expand_type(T&&...) {}
};
}