I am trying to specialize a templated function with a functional argument, but the compiler is unable to infer the proper specialization. I get compiler errors unless I am explicit about the function type.
#include <functional>
int data;
using MyFunction = std::function<int()>;
template<class T> void set(const T& value)
{
data = value;
}
template<> void set<MyFunction>(const MyFunction& f)
{
data = f();
}
int main()
{
set(1); // OK
MyFunction f2 = []() { return 1; }; // OK
set(f2);
auto f1 = []() { return 1; }; // COMPILE ERROR
set(f1);
set([]() { return 1; }); // COMPILE ERROR
return 0;
}
The compiler error (g++/c++11):
main.cpp: In instantiation of ‘void set(const T&) [with T = main()::<lambda()>]’:
main.cpp:26:8: required from here
main.cpp:10:10: error: invalid user-defined conversion from ‘const main()::’ to ‘int’ [-fpermissive]
10 | data = value;
| ~~~~~^~~~~~~
main.cpp:25:15: note: candidate is: ‘constexpr main()::::operator int (*)()() const’ (near match)
25 | auto f1 = []() { return 1; }; // COMPILE ERROR
Example code includes a boiled-down version of my attempts. Tried C++11, 14, 17, 20 with same results. MSVC 16 and g++, similar results.
I'm hoping/expecting the compiler to correctly infer the lambda function type and pick the correct specialization.
I'm hoping/expecting the compiler to correctly infer the lambda function type and pick the correct specialization.
The problem is that implicit conversions are not considered during template argument deduction.
To solve this either just provide ordinary non-template overload instead of specialization or an alternative is to use std::is_invocable_r
along with constexpr if
as shown below:
Here we use a non-template overload and requires
(in C++20). Note that it is trivial to change(if needed) requires
to SFINAE and is left as an exercise.
template<class T> void set(const T& value) requires(!std::is_invocable_v<T>)
{
data = value;
}
//non template overload
void set(const MyFunction& f)
{
data = f();
}
Here we use constexpr if
along with std::is_invocable_v
.
template <class T> void set(T const& value)
{
if constexpr (std::is_invocable_v<T>)
{
data = value();
}
else
{
data = value;
}
}