Reputation: 1897
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.
Upvotes: 3
Views: 323
Reputation: 50568
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.
Upvotes: 2
Reputation: 41331
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>)>;
//...
};
Note, however, that with this approach the widening conversions won't work as well:
// doesn't compile
Thing<int> foo{[](long long bar){});
Upvotes: 3