How to define template parameters for a generic la

2020-04-18 07:09发布

Explanation:

CLion and it's standard compiler give me an error that the "candidate template [is] ignored", when I write a lambda as parameter for a generic function that takes a lambda as argument. This lambda takes a generic type T and returns another unknown type A.

The container class that I am writing is supposed to support functional operations like these in Scala or the ones from the Java Stream API.

To be exact: The map function makes huge problems. It is implemented as a member function in a class called Sequence, which takes a generic parameter T. It is supposed to take an element of an already known type T ( actually it iterates through the whole sequence ) and convert it into an unknown type A. The implementation itself is not the problem, but I cannot call the function with the lambda syntax I know.

Code:

Sequence.h

template< typename T >
class Sequence {
public:
    template< typename A >
    auto map( std::function< A( const T ) > function ) const {
        auto sequence = new Sequence< A >;
        for ( const T& element : *this ) {
            sequence->push( function( element ) );
        }
        return *sequence;
    }
}

main.cpp

int main() {
    Sequence< uint32_t > a;
    a.push( 20 );
    a.push( 30 );
    a.push( 40 );

    a.map( []( uint32_t c ) -> uint32_t {
        return c * c;
    } );
    return 0;
}

As far as I understand a lambda gets initialized, which takes a parameter of type std::uint32_t and returns a value of type std::uint32_t. The generic parameter A doesn't seem to get inferred at this point.

Error Stack:

main.cpp:21:7: error: no matching function for call to 'Sequence<unsigned int>::map(main()::<lambda(uint32_t)>)'
     } );

Sequence.h:143:10: note: candidate: template<class A> auto Sequence<T>::map(std::function<A(T)>) const [with A = A; T = unsigned int]
     auto map( std::function< A( const T ) > function ) const {

note:   template argument deduction/substitution failed:
main.cpp:21:7: note:   'main()::<lambda(uint32_t)>' is not derived from 'std::function<A(unsigned int)>'
     } );

Thanks in advance!

2条回答
倾城 Initia
2楼-- · 2020-04-18 07:20

As already pointed out by @cpplearner in the comments above, the reason why this won't work is explained in detail here: Constructing std::function argument from lambda.

If your Sequence is already a template, there should be no reason to require the callable for your map function to be passed in the form of an std::function. At least, I seem unable to come up with a reason that could justify doing this. std::function is generally not free to construct or call, and also can inhibit inlining. Best avoid it unless you really need the capability to store arbitrary callables for later use. Simply take a forwarding reference, e.g.:

template <typename F>
auto map(F&& f) const;

You should be able to deduce the result type of whatever invoking f on an element of your sequence ends up producing, e.g., via

decltype(f(std::declval<T>()))

Furthermore, don't just return a raw pointer to a new Sequence. Use an std::unique_ptr.

Putting it all together, your map function could look something like this:

template <typename F>
auto map(F&& f) const
{
    using output_element_type = decltype(f(std::declval<T>()));

    auto sequence = std::make_unique<Sequence<output_element_type>>();

    for (const T& element : *this)
        sequence->push(f(element));

    return sequence;
}
查看更多
狗以群分
3楼-- · 2020-04-18 07:23

Ignoring the const problem, you have a sort of chicken-and-egg problem.

It's true that your lambda can be converted to a std::function<std::uint32_t(std::unit32_t)>.

But it's also true that the lambda isn't a std::function<std::uint32_t(std::unit32_t)> so the compiler can't deduce A.

And if the compiler can't deduce A, can't convert the lambda to std::function<A(T)>.

You obviously can explicit the correct std::function type calling map()

a.map(std::function<std::uint32_t(std::uint32_t)>{[]( uint32_t c ) -> uint32_t {
    return c * c;
}});

and, taking in count that you're using C++17 (so you can use the deduction guides for std::function) also deducing the template parameters for std::function

a.map(std::function{[]( uint32_t c ) -> uint32_t {
    return c * c;
}});

but, using again the template deduction guides for std::function, what about writing mat() to accept a simply callable and deducing A from it?

I mean... what about something as follows?

template <typename F>
auto map( F && func ) const {
    using A = typename decltype(std::function{std::forward<F>(func)})::result_type;

    auto sequence = new Sequence< A >;
    for ( const T& element : *this ) {
        sequence->push( std::forward<F>(func)( element ) );
    }
    return *sequence;
}

(caution: code not tested).

You can also deduce A, without std::function deduction guides (so before C++17), as suggested by Michael Kenzel.

查看更多
登录 后发表回答