Last active
October 10, 2019 08:31
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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