Как понять этот синтаксис C ++? [Дубликат]

Лучший способ получить переменную класса напрямую связан с именем класса

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1
16
задан Yakk - Adam Nevraumont 17 June 2015 в 14:34
поделиться

2 ответа

Короткий ответ: «Это не очень хорошо».

Он вызывает 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), имеющего нетривиальный конструктор копирования, нетривиальный конструктор перемещения или нетривиальный деструктор без соответствующего параметра, условно поддерживается семантикой, определяемой реализацией.

Таким образом, проблемы этого кода заключаются в том, что порядок не задан и , он полагается на варианты выбора подходящего варианта компилятора или .

Мы можем исправить проблему 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 является то, что не все компиляторы, подобные ему, - расширение пакета параметров, содержащего инструкцию, содержащую целые подзапросы, не является тем, что они ожидают сделать.

25
ответ дан Edgar Rokjān 22 August 2018 в 09:40
поделиться
  • 1
    [expr.call] / p7 говорит, что «Передача потенциально оцененного аргумента типа класса (раздел 9), имеющего нетривиальный конструктор копии, нетривиальный конструктор перемещения или нетривиальный деструктор без соответствующего параметра, условно поддерживается с семантикой, определенной реализацией. & quot; Это все еще плохо, но не UB. – T.C. 23 January 2015 в 20:35
  • 2
    @ T.c. как это отличается от UB? Я предполагаю, что это условно разрешено в constexpr, где UB запрещен. Реализации всегда были свободны определять UB, но они хотят (за исключением constexpr?) – Yakk - Adam Nevraumont 23 January 2015 в 20:39
  • 3
    Использование чего-то условно-поддерживаемого, которое не поддерживается конкретной реализацией, требует, чтобы реализация выдавала диагностику. И реализация необходима для документирования всех условно-поддерживаемых, которые она не поддерживает. (И поскольку семантика здесь определяется реализацией, когда она поддерживается, это должно быть документировано также.) – T.C. 23 January 2015 в 20:41
  • 4
    @ T.C. ОК. Думаю, я добавил достаточно ласковых слов, чтобы сделать ситуацию ясной, плюс цитата, которую вы нашли в стандарте. Учитывая, насколько легко устранить проблему (приведение void), это действительно должно быть сделано. Кроме того, operator, может вызвать неожиданные побочные эффекты, если они будут переопределены. – Yakk - Adam Nevraumont 23 January 2015 в 21:05
  • 5
    Er ... предполагая, что нет пользовательских operator,, исходный код не передает какие-либо типы не-POD, не так ли? Каждый аргумент - это просто int со значением нуля. – hvd 23 January 2015 в 21:24

Фактически он вызывает функцию 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)...};
13
ответ дан Community 22 August 2018 в 09:40
поделиться
  • 1
    Но, полагаю, порядок, в котором выполняются вызовы, произволен / неопределенен? – rubenvb 23 January 2015 в 14:18
  • 2
    @rubenvb Правильно. Неопределенные. – Barry 23 January 2015 в 14:21
  • 3
    @NoSenseEtAl Потому что он должен принимать один 0 для каждого аргумента, который вы проходите. Невозможно написать & quot; лямбда, которая принимает N args & quot; так что это просто «лямбда, которая берет что-нибудь». – Barry 23 January 2015 в 14:23
  • 4
    @NoSenseEtAl Если вам интересно, с чем это связано (это , 0), вы не можете иметь последовательность void, void, void, если f возвращает void, но вы можете игнорировать то, что возвращает f, и использовать определенное значение. – WhozCraig 23 January 2015 в 14:25
  • 5
    Пара (void) бросает, чтобы защитить от перегруженных операторов запятой, не повредит. – T.C. 23 January 2015 в 17:35
Другие вопросы по тегам:

Похожие вопросы: