C ++ потрійне призначення лямбда


11

Будь-яка ідея, чому наступний фрагмент не збирається? Він скаржиться на помилку "помилка: операнди до?: Мають різні типи"

  auto lambda1 = [&](T& arg) {
      ...
  };
  auto lambda2 = [&](T& arg) {
      ...
  };
  auto lambda = condition ? lambda1 : lambda2;

Відповіді:


11

Окремі лямбди перекладачем перекладаються в різні класи. Наприклад, визначення lambda1 еквівалентно:

class SomeCompilerGeneratedTypeName {
public:
  SomeCompilerGeneratedTypeName(...) { // Capture all the required variables here
  }

  void operator()(T& arg) const {
    // ...
  }

private:
  // All the captured variables here ...
};

Тому компілятор генерує два різні типи, що викликає несумісність типу для auto lambda = condition ? lambda1 : lambda2;

Працює таке:

auto lambda = condition ? std::function<void(T&)>(lambda1) : std::function<void(T&)>(lambda2);

Щоб підкреслити, що обидва лямбда - це дійсно різні типи, ми можемо використовувати <typeinfo>зі стандартної бібліотеки та typeidоператора. Лямбди не є поліморфними типами, тому стандарт гарантує, що оператор 'typeid' оцінюється під час компіляції. Це показує, що наступний приклад дійсний, навіть якщо RTTI вимкнено:

#include <iostream>
#include <typeinfo>

int main()
{
    struct T {

    };

    auto lambda1 = [&](T& arg) {
        return;
    };

    auto lambda2 = [&](T& arg) {
      return;
    };

    std::cout << typeid(lambda1).name() << "/" << typeid(lambda1).hash_code() << std::endl;
    std::cout << typeid(lambda2).name() << "/" << typeid(lambda2).hash_code() << std::endl;

    return 0;
}

Вихід програми (з GCC 8.3, див. На Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066

Повна помилка - "помилка: операнди до?: Мають різні типи 'f (const std :: вектор <int> &, size_t, size_t) [з T = неподписаний char; size_t = довго непідписаний int] :: <lambda (неподписаний char & )> 'і' f (const std :: вектор <int> &, size_t, size_t) [з T = неподписаний char; size_t = довго непідписаний int] :: <lambda (неподписаний char &)> '", в якому я бачу однакові всі типи та формати.
корова

1
@cow Оскільки лямбда-файли самі по собі мають однаковий підпис, тому компілятор, щоб приховати деталі її реалізації та дати більш зрозумілу помилку, дає вам розташування та підпис обох лямбда, які є ідентичними. Але врешті-решт, вони все ще трактуються як SomeCompilerGeneratedTypeName1іSomeCompilerGeneratedTypeName2
Xatyrian

1
@cow Я додав приклад, який висвітлює початок відповіді, вам може бути цікаво
Xatyrian

12

Цікаво, що якщо лямбдас не захоплює, +може бути використана хитрість оператора :

auto lambda1 = [](int arg) { ... };
auto lambda2 = [](int arg) { ... };

auto lambda = condition ? +lambda1 : +lambda2; // This compiles!
lambda(2019); 

Це працює, тому що +перетворить лямбда в функціональний покажчик, і обидва функціональні вказівники мають один і той же тип (щось подібне void (*)(int)).

Якщо GCC та Clang (але не з MSVC) +можуть бути опущені, лямбдати все одно перетворяться на функціональні покажчики.


1
На візуальній студії це не працюватиме. Їх розширення, що дозволяють лямбда перетворюватися на різні конвекції викликів, перешкоджає цьому.
Гійом Ракікот

@GuillaumeRacicot, дякую за цю замітку. Скажіть, будь ласка, посилання, де я можу прочитати більше про це?
Evg


2
@GuillaumeRacicot Це, здається, збирається на останній версії MSVC. godbolt.org/z/ZQLWxy
Брайан

@Brian Oh! Це відмінна новина. Тепер я повинен змінити якийсь код. Дякую!
Гійом Ракікот

10

Компілятор не може визначити, який тип autoповинен бути:

auto lambda = condition ? lambda1 : lambda2;

оскільки кожна лямбда має різний та унікальний тип.

Один із способів роботи - це:

auto lambda = [&](T& arg) {
     return (condition ? lambda1(arg) : lambda2(arg));
}

8

Він не компілюється, оскільки кожна лямбда має унікальний тип, для неї не існує загального типу ?:.

Ви можете їх загорнути std::function<void(T&)>, наприклад

auto lamba1 = [&](T& arg) {
  ...
};
auto lambda2 = [&](T& arg) {
  ...
};
auto lambda = condition ? std::function(lambda1) : lambda2; // C++17 class template deduction

8

Оскільки 2 лямбда ( lambda1і lambda2) є двома різними типами, ?:не можна вивести тип повернення для lambdaзlambda1 і lambda2. Це відбувається тому, що ці 2 не конвертуються один в одного.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.