I am attempting to create a wrapper around an std::function
for reasons not explained here. I know that the following code works.
std::function<void()> function = [&]() -> void {};
The following is my wrapper around the std::function
, however, it does not work when I try to construct it with a lambda.
template<typename Type, typename ...Args>
class custom_function {
public:
using function_type = std::function<Type(Args...)>;
custom_function(const function_type &other) {
function = other;
}
private:
function_type function;
};
// error: conversion from 'main()::<lambda()>' to non-scalar type 'custom_function<void>' requested
custom_function<void> function = [&]() -> void {
};
I thought that this would work since a lambda can be assigned to a std::function
. If I add the following constructor, the code now compiles.
template<typename Type, typename ...Args>
class custom_function {
// ...
template<typename Lambda>
custom_function(const Lambda &other) {
function = other;
}
// ...
};
// this is now valid
custom_function<void> function = [&]() -> void {
};
Why does this constructor work but the previous constructor did not? Why is...
custom_function(const function_type &other) {
function = other;
}
different from...
template<typename Lambda>
custom_function(const Lambda &other) {
function = other;
}
I'm compiling with C 17 using G on Windows. Thanks
CodePudding user response:
It's one too many implicit conversion steps. Generally speaking, C will let you get away with one level of indirection. So we could call a function that expects a double
and pass it an int
, and things will work fine, because there's an implicit conversion from double
to int
. Now let's look at your code.
custom_function(const function_type &other) {
function = other;
}
This is a converting constructor, which is just a fancy way of saying it's a constructor that takes one argument of some other type. When we write
custom_function<void> f = [&]() -> void {
};
This is a copy initialization. In principle, it's constructing something on the right hand side and then assigning it to the left. Since we don't explicitly call a constructor on the right hand side, we have to convert our lambda into custom_function<void>
. We could do that if either
- There was an conversion
operator
tocustom_function<void>
defined on the right hand type (which will never be the case here, since the right hand type is a lambda type), or - There was a converting constructor on
custom_function<void>
that takes a lambda as argument. This won't work here, since the only constructor takes astd::function
. And we're already talking about a conversion, so we're not going to consider doing another conversion to get tostd::function
.
When you replace your constructor with a template function that can take any type, then that template function suffices as a valid conversion from the lambda type directly to your custom function type.
Note that you can also use brace initialization to directly call your constructor. This will work with either constructor.
custom_function<void> f { [&]() -> void {} };
That's because this is an actual explicit constructor call and therefore will consider implicit conversions to get from the argument type to the declared constructor parameter type.
See also this discussion on the different initialization techniques.