Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent wrapping lambda in std::function with different types

Tags:

c++

c++11

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.

like image 993
moof2k Avatar asked Dec 11 '25 03:12

moof2k


2 Answers

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){});
like image 108
Anton Savin Avatar answered Dec 13 '25 16:12

Anton Savin


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.

like image 28
skypjack Avatar answered Dec 13 '25 15:12

skypjack



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!