How to write a lambda function that can only take in int or float pointers?

Viewed 102

I need to create a lambda function that can take in either a int * or a float *, e.g., something like

auto func = [](void* ptr) {
  // do some assignments with the pointer
}

Currently, I am using the void * approach, but I'm wondering what are some other approaches to accomplish what I want?

I don't have access to C++17 features in my codebase so will have to stick to C++14 or lower semantics.

3 Answers

C++14 has generic lambdas, meaning you can use SFINAE on them, e.g. in their trailing return types:

#include <type_traits>

template <typename T> struct valid_ptr_type : std::false_type {};
template <> struct valid_ptr_type<int *>    : std::true_type {};
template <> struct valid_ptr_type<float *>  : std::true_type {};

template <typename T> constexpr bool valid_ptr_type_v{valid_ptr_type<T>::value};

int main() {
  // only allow ptr args that fulfills
  // the valid_ptr_type_v<decltype(ptr)> trait
  auto func = [](auto *ptr) ->
      std::enable_if_t<valid_ptr_type_v<decltype(ptr)>> {
          // do stuff with ptr
      };  // note: void return type (std::enable_if default)

  int a{};
  float b{};
  char c{};
  func(&a);
  func(&b);
  // func(&c);  // error
}

What this does behind the hood is to constrain the deduced single template parameter of the template function call operator of the lambda's closure type.

// ~ish
struct AnonClosureTypeOfGenericLambda {
    template <typename T, 
              typename = std::enable_if_t<valid_ptr_type_v<decltype(T*)>>>
    void operator()(T* ptr) const { /* ... */ }
};

By using the lambda overload trick:

template<typename... Ts> struct overload : public Ts... { using Ts::operator()...; };
template<typename... Ts> overload(Ts...)->overload<Ts...>;

auto f = overload([](int *) { /* do something with int-pointer */ }
                , [](float *){ /* do something with void-pointer */ });

But that's C++17. Here would be the C++11 solution:

template <class... Fs>
struct overload_t;

template <typename F, typename ... Fs>
struct overload_t<F, Fs...> : F, overload_t<Fs...>
{
    overload_t(F f, Fs... fs) : F(std::move(f)), overload_t<Fs...>(std::move(fs)...) {}

    using F::operator();
    using overload_t<Fs...>::operator();
};

template <typename F>
struct overload_t<F> : F
{
    overload_t(F f) : F(std::move(f)) {}

    using F::operator();
};

template <typename... Fs>
overload_t<Fs...> overload(Fs... fs)
{
    return overload_t<Fs...>(std::move(fs)...);
}

I'd recommend using static_assert Has clear code and compile errors. Also, while not the question, since both types are ptrs to float or int, you can use a conditional to do a different calculation based on the actual type.

#include <type_traits>
int main()
{
    auto foo = [](auto p) {
        static_assert(std::is_same_v<decltype(p), float*> || std::is_same_v<decltype(p), int*>);
        if (std::is_same_v<decltype(p), int*>)
            *p = 42;
         else
            *p = 42.1f;
    };
    float f{};
    int i{};
    double d{};

    // compiles
    foo(&f);
    foo(&i);
    // doesn't compile
    //foo(&d);
}
Related