I have a class that takes in its constructor a std::function<void(T)>. If I declare T to be a double, but pass the constructor a lambda that takes an int, the compiler let's me get away without a warning. (Update: MSVC issues a warning, clang does not). Is it possible to modify this code so that I get a compiler error (w/ clang)?
I was surprised this works:
#include <functional>
#include <iostream>
template <typename T>
class Thing {
public:
using Handler = std::function<void(T)>;
Thing(Handler handler)
: handler_(handler)
{}
Handler handler_;
};
int main() {
Thing<double> foo([](int bar) { // Conversion to void(int) from void(double)
std::cout << bar;
});
foo.handler_(4.2);
return 0;
}
...and compiles without a warning:
$ clang --std=c++11 -lc++ -Wall -Werror test.cc
$ ./a.out
4
It seems like such a conversion could result in unwanted side effects. I can't imagine a case where this would ever be desired behavior.
You can use for the parameter a template class which allows conversion only to T:
template <typename T>
struct Strict {
// This is a bit simplistic, but enough for fundamental types
Strict(T t_) : t(t_) {}
operator T() const { return t; }
template<typename U>
operator U() const = delete;
T t;
};
template <typename T>
class Thing {
public:
using Handler = std::function<void(Strict<T>)>;
//...
};
DEMO
Note, however, that with this approach the widening conversions won't work as well:
// doesn't compile
Thing<int> foo{[](long long bar){});
For non capturing lambdas, you can rely on the fact that they decay to pointers to functions.
Use this line:
using Handler = void(*)(T);
Instead of the current definition of Handler.
This way you'll receive an error, unless you change the argument type to the expected type.
In other words, this (does not) work as expected:
#include <functional>
#include <iostream>
template <typename T>
class Thing {
public:
using Handler = void(*)(T);
Thing(Handler handler)
: handler_(handler)
{}
Handler handler_;
};
int main() {
// switch the type of bar to double and it will compile
Thing<double> foo([](int bar) {
std::cout << bar;
});
foo.handler_(4.2);
return 0;
}
Be aware that this solution no longer works if you have a capture list for your lambdas.
Anyway, it solves the issue as explained in the question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With