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
Xatyrian
источник
Полная ошибка: «ошибка: операнды??: Разные типы» f (const std :: vector <int> &, size_t, size_t) [с T = беззнаковый символ; size_t = длинный беззнаковый int] :: <лямбда (беззнаковый символ & )> 'и' f (const std :: vector <int> &, size_t, size_t) [с T = unsigned char; size_t = длинное unsigned int] :: <lambda (unsigned 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) +можно не указывать, лямбда-выражения будут по-прежнему преобразовываться в указатели функций.

Evg
источник
1
Это не будет работать на визуальной студии, хотя. Их расширение, позволяющее лямбде переходить в другую вызывающую конвекцию, предотвращает это.
Гийом Расикот
@GuillaumeRacicot, спасибо за эту заметку. Не могли бы вы дать ссылку, где я могу прочитать больше об этом?
Evg
3
Вот и все
Гийом Расикот
2
@GuillaumeRacicot Похоже, что он компилируется в последней версии MSVC. godbolt.org/z/ZQLWxy
Брайан,
@ Брайан О! Это отличные новости. Теперь я должен изменить код. Спасибо!
Гийом
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
Caleth
источник
8

Поскольку 2 лямбда-выражения ( lambda1и lambda2) - это два разных типа, ?:невозможно определить тип возвращаемого значения для lambdafrom lambda1и lambda2. Это происходит потому, что эти 2 не конвертируемы друг в друга.

Afshin
источник