Вывод лямбда-типа - PullRequest
       75

Вывод лямбда-типа

0 голосов
/ 29 августа 2018
auto dothings = [](long position) {
auto variable;
/*do things*/
return variable;
};

float x = dothings(1l);
char  y = dothings(2l);

В сущности, меня интересует, возможно ли каким-либо образом переменная внутри лямбды определить тип, которому присваивается возвращаемое значение, в этой ситуации это float и char. Есть ли эквивалент шаблона typename? Благодаря.

Ответы [ 3 ]

0 голосов
/ 29 августа 2018

Это можно сделать, но это а) довольно сложно, и б) не очень хорошая идея, 99,9% времени. Вот как вы продолжаете. Единственный способ сделать что-либо, основываясь на типе, которому вы назначаете выражение, - это воспользоваться преимуществами неявных преобразований. Для этого требуется шаблонный неявный оператор диалога, который не может быть объявлен локально в лямбде, поэтому мы должны начать с написания небольшого количества кода поддержки:

template <class T>
struct identity {
    T get(); // not defined
};


template <class F>
struct ReturnConverter {

    F f;

    template <class T>
    operator T() {
        return f(identity<T>{});
    }
};

template <class F>
auto makeReturnConverter(F f) { return ReturnConverter<F>{f}; }

Первый класс - просто помочь лямбде с выводящими типами. Второй класс - тот, который сам принимает лямбду (или любой вызываемый) и имеет неявный оператор преобразования для любого типа. Когда запрашивается преобразование, оно вызывает вызываемый объект, используя наш шаблон класса identity как способ подачи типа. Теперь мы можем использовать это так:

auto doIt = [] (long l) {
    return makeReturnConverter([=] (auto t) {
        return l + sizeof(decltype(t.get()));
    });
};

Эта лямбда создает наш специальный класс ReturnConverter, кормя другую лямбду. Эта лямбда захватывает аргумент long l внешней лямбды (по значению), и она готова принять наш специальный класс идентичности в качестве единственного аргумента. Затем он может отказаться от «цели» или типа назначения. Я использую sizeof здесь, чтобы быстро показать пример, где результат зависит как от аргумента лямбда-выражения, так и от целевого типа. Но учтите, что как только я получу тип, используя decltype(t.get()), я смогу объявить переменную этого типа и сделать с ней все, что захочу.

float x = doIt(5);
double y = doIt(2);

После этих вызовов x будет 9 (число с плавающей запятой 4, + 5) и y будет 10 (double это размер 8, + 2).

Вот более интересный пример:

auto doIt2 = [] (long l) {

    return makeReturnConverter([=] (auto t) {
        decltype(t.get()) x;
        for (std::size_t i = 0; i != l; ++i ) {
            x.push_back(i*i);
        }
        return x;
    });
};

std::vector<int> x = doIt2(5);
std::deque<int> y = doIt2(5);

Здесь я могу собрать стандартный контейнер в соответствии с тем, что запрашивает левая сторона, если в стандартном контейнере есть метод push_back.

Как я уже сказал, для начала это чрезмерно сложно и трудно оправдать в подавляющем большинстве случаев. Обычно вы можете просто указать конкретный тип в качестве явного (не выводимого) параметра шаблона и auto слева. Я использовал это в очень определенных случаях все же. Например, в gtest, когда вы объявляете тестовые фикстуры, любые повторно используемые данные объявляются как переменные-члены в фикстуре. Нестатические переменные-члены должны быть объявлены с их типом (не может использовать auto), поэтому я использовал этот трюк, чтобы позволить быстро создавать определенные типы данных фикстуры, сохраняя при этом повторение как можно ближе к нулю. В данном случае это нормально, потому что код, код которого не обязательно должен быть очень надежным, но действительно хочет свести к минимуму повторение практически любой ценой; обычно это не очень хороший компромисс (и с auto, доступным слева, обычно не требуется).

0 голосов
/ 29 августа 2018

Ответ здесь - НЕТ.

Возвращаемый тип основан на значениях, используемых внутри функции

auto dothings = [](long position) {
auto variable;        // This is illegal as the compiler can not deduce the type.
                      // An auto declaration needs some way for it to determine the type.
                      // So that when you use it below it can determine the
                      // return type of the function.
/*do things*/
return variable;
};

Оператор присваивания просматривает тип выражения результата, чтобы увидеть, есть ли какие-либо автоматические преобразования, которые можно применить для преобразования типа результата функции в место назначения типа присваивания.

char x = dothings(10); // the result of the expression on the right
                       // is converted to char before assignment. If
                       // it can't be converted then it is a compiler error.

Вы можете думать о лямбдах как о синтаксическом сахаре для создания функтора.

[<capture List>](<Parameter List>) {
    <Code>
}

То же, что и

struct <CompilerGeneratedName>
{
    <Capture List With Types>;
    Constructor(<Capture List>)
         : <Capture List With Types>(<Capture List>)
    {}
    <Calculated Return> operator()(<Parameter List>) const {
        <Code>
    }
}{<Capture List>};

Пример:

{
    int y = 4;
    auto l1 = [&y](int x){return y++ + x;}
    struct MyF
    {
        int& y;
        MyF(int& y)
            : y(y)
        {}
        int operator()(int x) const {
            return y++ + x;
        }
    };
    auto l2 = MyF(y);

    std::cout << l2(5) << " " << l1(5) << "\n";
}
0 голосов
/ 29 августа 2018

Единственный способ, которым я знаю, это использовать параметр шаблона:

template<typename T> 
T dothings(long value)
{
    T result;
    ...
    return result;
}

auto x = dothings<float>(1l);
auto y = dothings<char>(2l);

Шаблон также позволяет специализироваться на определенных типах. Поэтому, если вы хотите использовать другую логику для двойников, вы можете написать следующее:

template<>
double dothings<double>(long value)
{
    T result;
    ...
    return result * 2.0;
}
...