Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interaction of C++ lambdas and templates [duplicate]

I'm trying to wrap my mind around the interaction of C++ lambda expressions and templates.

This code works as I expect:

#include <iostream>

int bar (int x, int (* f) (int))
{
    return f (x);
}

double bar (double x, double (* f) (double))
{
    return f (x);
}

int main ()
{
    std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
    std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;

    return 0;
}

So does this:

#include <iostream>
#include <functional>

int bar (int x, std::function<int (int)> f)
{
    return f (x);
}

double bar (double x, std::function<double (double)> f)
{
    return f (x);
}

int main ()
{
    std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
    std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;

    return 0;
}

So far, so good. But neither of the following examples compile:

#include <iostream>

template <typename T>
T bar (T x, T (* f) (T))
{
    return f (x);
}

int main ()
{
    std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
    std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;

    return 0;
}

and

#include <iostream>
#include <functional>

template <typename T>
T bar (T x, std::function <T (T)> f)
{
    return f (x);
}

int main ()
{
    std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
    std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;

    return 0;
}

GCC version 8.3.0 (with -std=c++17) gives an error message:

no matching function for call to 'bar(int, main()::<lambda(int)>' (and
another for the "double" version) and "template argument
deduction/substitution failed: main()::<lambda(int)> is not derived
from std::function<T(T)>" (for the second failing example).

However, this assignment works:

std::function<int (int)> f = [] (int x) -> int { return x * x; };

Could anyone please shed light for me? (Obviously, this isn't practical code. This is just an attempt to learn.)

like image 215
Ken Ballou Avatar asked May 06 '20 03:05

Ken Ballou


2 Answers

The problem is that implicit conversion (from lambda to function pointer or std::function) won't be considered in template argument deduction; then in your 3rd and 4th sample type deduction for T on the 2nd function argument (i.e. the lambda) fails.

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

In this case T could be deduced from only the 1st function argument; you can declare the 2nd one with non-deduced context, to exclude it from deduction.

e.g.

template <typename T>
T bar (T x, std::type_identity_t<T (*) (T)> f)
{
    return f (x);
}

template <typename T>
T bar (T x, std::type_identity_t<std::function <T (T)>> f)
{
    return f (x);
}

PS: std::type_identity is available from C++20, before that you can make one by your own, it's not difficult.

LIVE

like image 74
songyuanyao Avatar answered Oct 19 '22 09:10

songyuanyao


Your failing code is expecting function pointers during template argument deduction and lambdas are not function pointers.

However, non-capturing lambdas such as yours can be converted to function pointers.

The most terse way to do this is to apply a unary +. This works because a + is valid for pointers, triggering a conversion before template substitution occurs.

#include <iostream>

template <typename T>
T bar (T x, T (* f) (T))
{
    return f (x);
}

int main ()
{
    std::cout << bar (16, +[] (int x) -> int { return x * x; }) << std::endl;
//                        ^ this is the only change
    std::cout << bar (1.2, +[] (double x) -> double { return x * x; }) << std::endl;
//                         ^ this is the only change

    return 0;
}

See it compile on godbolt

like image 5
Drew Dormann Avatar answered Oct 19 '22 07:10

Drew Dormann