Лучший способ получить переменную класса напрямую связан с именем класса
class Employee:
counter=0
def __init__(self):
Employee.counter+=1
Короткий ответ: «Это не очень хорошо».
Он вызывает f
для каждого из args...
и отбрасывает возвращаемое значение. Но это делает так, что в ряде случаев приводит к неожиданному поведению.
Код не имеет гарантий порядка, и если возвращаемое значение f
для данного Arg
имеет перегруженный operator,
может иметь неприятные побочные эффекты.
С некоторым белым пространством:
[](...){}(
(
f(std::forward<Args>(args)), 0
)...
);
Мы начнем изнутри.
f(std::forward<Args>(args))
- это неполное утверждение, которое можно разложить на ...
. Он будет вызывать f
на одном из args
при расширении. Вызовите это утверждение INVOKE_F
.
(INVOKE_F, 0)
принимает возвращаемое значение f(args)
, применяет operator,
, затем 0
. Если возвращаемое значение не имеет переопределений, это отбрасывает возвращаемое значение f(args)
и возвращает значение 0
. Назовите это INVOKE_F_0
. Если f
возвращает тип с переопределением operator,(int)
, здесь происходят плохие вещи, и если этот оператор возвращает не-POD-esque-тип, вы можете получить «условно поддерживаемое» поведение позже.
[](...){}
создает лямбда, которая принимает вариации C-стиля как единственный аргумент. Это не то же самое, что пакеты параметров C ++ 11, или C ++ 14 variadic lambdas. Возможно, незаконно передавать не-POD-esque типы в функцию ...
. Вызвать это HELPER
HELPER(INVOKE_F_0...)
- это расширение пакета параметров. в контексте вызова HELPER
's operator()
, который является юридическим контекстом. Оценка аргументов не определена, и из-за подписи HELPER
INVOKE_F_0...
, вероятно, должны содержаться только простые старые данные (в языке C ++ 03), или, более конкретно, [expr.call] / p7 говорит: (через @ TC)
Передача потенциально оцененного аргумента типа класса (раздел 9), имеющего нетривиальный конструктор копирования, нетривиальный конструктор перемещения или нетривиальный деструктор без соответствующего параметра, условно поддерживается семантикой, определяемой реализацией.
blockquote>Таким образом, проблемы этого кода заключаются в том, что порядок не задан и , он полагается на варианты выбора подходящего варианта компилятора или .
Мы можем исправить проблему
operator,
следующим образом:template <class F, class... Args> void for_each_argument(F f, Args&&... args) { [](...){}((void(f(std::forward<Args>(args))), 0)...); }
, тогда мы можем гарантировать порядок, разложив в инициализаторе:
template <class F, class... Args> void for_each_argument(F f, Args&&... args) { int unused[] = {(void(f(std::forward<Args>(args))), 0)...}; void(unused); // suppresses warnings }
, но приведенное выше не выполняется, когда
Args...
пуст, поэтому добавьте еще0
:template <class F, class... Args> void for_each_argument(F f, Args&&... args) { int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...}; void(unused); // suppresses warnings }
, и нет никакой веской причины для компилятора НЕ удалять
unused[]
из existance, а еще оцениватьf
вargs...
по порядку.Моим предпочтительным вариантом является:
template <class...F> void do_in_order(F&&... f) { int unused[] = {0, (void(std::forward<F>(f)()), 0)...}; void(unused); // suppresses warnings }
, который принимает нулевую лямбда и запускает их по одному, слева направо. (Если компилятор может доказать, что порядок не имеет значения, он может свободно вывести их из строя).
Затем мы можем реализовать выше:
template <class F, class... Args> void for_each_argument(F f, Args&&... args) { do_in_order( [&]{ f(std::forward<Args>(args)); }... ); }
, который помещает «странное расширение» в изолированную функцию (
do_in_order
), и мы можем использовать его в другом месте. Мы также можем написатьdo_in_any_order
, который работает аналогичным образом, но делаетany_order
понятным: однако, исключая экстремальные причины, код, выполняемый в предсказуемом порядке в расширении пакета параметров, уменьшает неожиданность и уменьшает головные боли до минимума.Недостатком метода
do_in_order
является то, что не все компиляторы, подобные ему, - расширение пакета параметров, содержащего инструкцию, содержащую целые подзапросы, не является тем, что они ожидают сделать.
Фактически он вызывает функцию f
для каждого аргумента в args
в неуказанном порядке.
[](...){}
создает лямбда-функцию, которая ничего не делает и получает произвольное количество аргументов (va args).
((f(std::forward<Args>(args)), 0)...)
аргумент lambda.
(f(std::forward<Args>(args)), 0)
вызывать f
с переадресованным аргументом, отправить 0
в lambda.
Если вы хотите, чтобы указанный заказ вы можете использовать следующее: :
using swallow = int[];
(void)swallow{0, (f(std::forward<Args>(args)), 0)...};
, 0
), вы не можете иметь последовательность void, void, void
, если f
возвращает void
, но вы можете i> игнорировать то, что возвращает f
, и использовать определенное значение.
– WhozCraig
23 January 2015 в 14:25
(void)
бросает, чтобы защитить от перегруженных операторов запятой, не повредит.
– T.C.
23 January 2015 в 17:35
constexpr
, где UB запрещен. Реализации всегда были свободны определять UB, но они хотят (за исключениемconstexpr
?) – Yakk - Adam Nevraumont 23 January 2015 в 20:39void
), это действительно должно быть сделано. Кроме того,operator,
может вызвать неожиданные побочные эффекты, если они будут переопределены. – Yakk - Adam Nevraumont 23 January 2015 в 21:05operator,
, исходный код не передает какие-либо типы не-POD, не так ли? Каждый аргумент - это простоint
со значением нуля. – hvd 23 January 2015 в 21:24