Skip to content

Instantly share code, notes, and snippets.

@hexagon62
Last active October 10, 2019 08:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hexagon62/7866ce2b8d6f863919a3c55d1b60d87c to your computer and use it in GitHub Desktop.
Save hexagon62/7866ce2b8d6f863919a3c55d1b60d87c to your computer and use it in GitHub Desktop.
Can the types of the parameters of a lambda be extracted/deduced in C++? As it turns out, yes! See the second file in the gist for how to do it.
#include <iostream>
#include <functional>
#include <tuple>
#include <string>
template<typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
// Let's say I have a class like this where I need to store heterogenous data
// I don't want to resort to type-erasure
template<typename... Ts>
class SomeContainer
{
public:
SomeContainer(Ts... args)
: data(args...)
{}
// However sometimes I need to access a certain types of data and pass it to a function
template<typename Ret, typename... Us>
void match(Ret(*f)(Us...))
{
// We pass every bit of data we requested from the tuple into the function
// We can only access tuple elements through the actual type, not a reference or const ref
// As such, we must change the template parameters to contain only the actual types
f(std::get<remove_cvref_t<Us>>(this->data)...);
}
// Like the above, but it works with pointers-to-members
template<typename T, typename Ret, typename... Us>
void match(Ret(T::*f)(Us...), T& instance)
{
(instance.*f)(std::get<remove_cvref_t<Us>>(this->data)...);
}
// This version uses std:function so that we can pass in a lambda
template<typename Ret, typename... Us>
void match2(std::function<Ret(Us...)> f)
{
f(std::get<remove_cvref_t<Us>>(this->data)...);
}
private:
std::tuple<Ts...> data;
};
// I have a couple of things I could pass into SomeContainer<Ts...>::actOnData as a functor
// The first is an ordinary function. Lets say this function needs access to any float or std::string the container may have
void printFloatAndString(float f, const std::string& s)
{
std::cout << "Ordinary function! " << f << " " << s << std::endl;
}
// Now let's say we have a method in some class and we'd pass in a pointer-to-member.
class Printer
{
public:
void printFloatAndString(float f, const std::string& s)
{
std::cout << "Member function! " << f << " " << s << std::endl;
}
};
int main()
{
// For simplicity's sake, I will have it contain an int, float and a string
SomeContainer<int, float, std::string> data(4, 3.14f, "test");
Printer p;
// The parameters of a pointed to function may be deduced
data.match(printFloatAndString);
// The parameters of a pointed-to-member may be deduced
data.match(&Printer::printFloatAndString, p);
// But, unless you explicitly specific the template parameters, you may not use a lambda
data.match2<void, float, const std::string&>([](auto f, auto&& s) {
std::cout << "Lambda! " << f << " " << s << std::endl;
});
// The following results in a compiler error, as the arguments cannot be deduced
// This is the syntax I want to be able to use.
/*
data.match2([](float f, const std::string& s) {
std::cout << "Lambda! " << f << " " << s << std::endl;
});
*/
// As a matter of fact, if you're forced to take an std::function,
// the parameters from the function used to construct the std::function cannot be deduced
/*
data.match2(printFloatAndString);
*/
// The problem more or less becomes how we can get around this limitation of std::function
// rather than purely how to make it work for lambdas
}
#include <iostream>
#include <functional>
#include <tuple>
#include <string>
template<typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template<typename... Ts>
class SomeContainer
{
public:
SomeContainer(Ts... args)
: data(args...)
{}
template<typename Lambda>
void match(Lambda l)
{
// Since lambdas are basically nameless/classes structs that overload operator()
// we can get a pointer-to-member to its operator() overload!
this->invoke(&decltype(l)::operator(), l);
}
private:
// And now we can access the types of the parameters!
// It's important that the pointer-to-member is const-qualified.
// Lambdas by default have a const-qualified operator() overload.
// The main limitation of this is that only one operator() overload may exist.
template<typename LambdaT, typename Ret, typename... Us>
void invoke(Ret(LambdaT::* f)(Us...) const, LambdaT& l)
{
(l.*f)(std::get<remove_cvref_t<Us>>(this->data)...);
}
std::tuple<Ts...> data;
};
int main()
{
SomeContainer<int, float, std::string> data(4, 3.14f, "test");
data.match([](int i, const std::string& s) {
std::cout << "Lambda! " << i << " " << s << std::endl;
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment