Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"candidate template ignored: could not match ..." for template function argument to template function

overview

i am attempting to pass a lambda to a template function which takes a template function type. on compilation is errors with candidate template ignored: could not match....

however, when i try to pass the same lambda to a template class which takes a template function type, it compiles and works.

questions

  1. why does this work for a template class but not a template function?
  2. is there a way to make this work?
  3. what is the extra expected parameter to the template function type when i try this with the template function (see below for more details)?

code

consider the following code (c++17)

#include <functional>

// define the template function type
template<typename T, typename...S>
using compose_fn_t = std::function<T(S...)>;


// define a template function which accepts the template function type
template<typename T, typename... S>
void compose(compose_fn_t<T, S...> fn) {};

// define a template class which accepts the template function type
template<typename T, typename... S>
class Compose {
public:
  Compose(compose_fn_t<T, S...> fn) {};
};

// some arbitrary types
struct A{};
struct B{};
struct C{};

int main() {

  compose<A, B, C>
    ([] (B b, C c) -> A { return {}; });  // this will not compile!

  Compose<A, B, C>
    ([] (B b, C c) -> A { return {}; });  // this compiles and runs correctly!

  return 0;
}

when i compile using compose<A, B, C>, it throws the following error

$ g++ -std=c++17 -o main main.cpp                                                                                                  
main.cpp:18:3: error: no matching function for call to 'compose'
  compose<A, B, C>
  ^~~~~~~~~~~~~~~~
main.cpp:8:6: note: candidate template ignored: could not match 'function<A (B, C, type-parameter-0-1...)>' against '(lambda at main.cpp:19:6)'
void compose(compose_fn_t<T, S...> fn) {
     ^
1 error generated.

what is this additional type-parameter-0-1 type that is expected by the template function type (compose_fn_t)?

like image 839
coco Avatar asked Dec 28 '25 22:12

coco


1 Answers

If you specify a template argument list for a function call, as in compose<A, B, C>, then this list is considered partial if there are more template parameters than arguments.

Calling the function still does template argument deduction on the remaining template parameters.

In your case the remaining parameters of the parameter pack are deduced against the compose_fn_t parameter (with the first three template arguments already determined), but that fails, because the lambda cannot be deduced to a std::function type.

You need to force the template parameters used in the function parameter into a non-deduced context in order to avoid this. One way is to use

template<typename T, typename... S>
void compose(typename std::type_identity<compose_fn_t<T, S...>>::type fn) {};

since everything to the left-hand side of the scope resolution operator :: is non-deduced. This does however also mean, that you will not be able to call the function without a template argument list.

std::type_identity is a C++20 feature, but you can easily implement your own. It does nothing but return the type given to it in its type member:

template<typename T>
struct type_identity {
    using type = T;
};

Alternatively take the argument by forwarding reference and forward it into a std::function construction in the body of the function, avoiding any deduction:

template<typename T, typename... S, typename F>
void compose(F&& f) {
    compose_fn_t<T, S...> fn{std::forward<F>(f)};
    // Use fn as before
};

This is not a problem with the class template, because class template argument deduction (CTAD) is only performed if no template argument list is provided at all.


You can also use CTAD on std::function to choose the correct std::function specialization for you, without having to repeat the types:

compose(std::function{[] (B b, C c) -> A { return {}; }});

You can also move this construction into the compose definition:

template<typename F>
void compose(F&& f) {
    auto fn = std::function{std::forward<F>(f)};
    // use fn here as before
};

so that the call

compose([] (B b, C c) -> A { return {}; });

is sufficient.

Note that both of these cases don't work with generic lambdas and also note that they don't work if you replace std::function with your compose_fn_t alias, because CTAD is not done on aliases.

like image 93
walnut Avatar answered Dec 31 '25 13:12

walnut



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!