Consider the following lines of code:
template <typename B>
float cannot_change(float second) {
B b(second);
return b();
}
int main() {
static float global = 10;
struct C {
float first_ = global;
float second_;
C(float second) : second_(second) {}
float operator()() { return first_ * second_; }
};
std::cout << cannot_change<C>(20) << std::endl;
}
The function cannot_change
constructs a template argument B
with one parameter. The function is fixed and cannot be changed but I know its source code. Suppose I want to pass a struct that takes two member functions (C
) in this example. I have created the struct C
which uses the static variable global
.
I have a few questions:
C
as I did or should I refrain from doing this? It seems strange, but I would like to use an element of type B
inside cannot_change
with two member variables.first_
for C
than I did? I prefer to copy the variable. Removing the static
keywords leads to compile errors.It looks like what you are trying to do is a best done with a lambda capture, like this:
#include <iostream>
int main()
{
const float local = 10;
auto f = [local](float second) { return local * second; };
std::cout << f(20) << std::endl;
return 0;
}
You can make a template lambda too if you need to deal with some class's members (so instead of float second
, you would write const B & b
and use b.member
in the function's body), like this:
#include <iostream>
int main()
{
const float local = 10;
struct Something {
float member1;
float member2;
};
auto f = [local]<typename B>(const B & b) { return local * b.member1 + b.member2; };
std::cout << f(Something{3, 14}) << std::endl;
return 0;
}
If you really need to call cannot_change
with this interface, then you can workaround it, like this:
#include <iostream>
#include <functional>
// You can't change this, so you can't use a lambda directly here
template <typename B>
float cannot_change(float second) {
B b(second);
return b();
}
// Here's the function signature of the lambda (and the functor B)
typedef float (*func_type)(float);
namespace details
{
// Here the idea is to capture the lambda in a std::function static instance.
// We are not capturing the context of the lambda but the lambda instance itself
template <typename T>
struct CaptureLambda {
// That's the function pointer that'll be used by the Adapter class.
// It's instantiated by the compiler on line 35
static float adapter(float f) {
// Actually call the std::function => lambda with the "second" parameter of B
return get()(f);
};
// Usual trick to create a static instance of something in a template class
static std::function<float(float)> & get() { static std::function<float(float)> t; return t; }
// Set the std::function instance to point to the lambda
static void set(T & t) { CaptureLambda::get() = t; }
};
}
// This is the adapted class that's following the expectation of
// "cannot_change" function. It has compile-time captured the CaptureLambda::adapter function
template <func_type func>
struct Adapter
{
// Store the result of the computation of the lambda
float result;
// Simplest solution. You could also compute the result in the () operator, but you'd need to store the parameter
Adapter(float param) : result(func(param)) {}
float operator ()() const { return result; }
};
int main()
{
// Here's a local you want to capture
const float local = 10;
// Here's the lambda that's doing the work
auto f = [local](float second) -> float { return local * second; };
// Because I'm lazy typing long types, create an alias.
typedef details::CaptureLambda<decltype(f)> CL;
// Need to capture the lambda instance here
CL::set(f);
// And finally call the cannot_change template function
std::cout << cannot_change<Adapter<CL::adapter>>(20) << std::endl;
return 0;
}
Unlike the comments above, you don't need to store the local global
variable in a static variable (with all the trouble it makes).
Instead, this uses a lambda to capture the local variable. The lambda function instance is captured by a template CaptureLambda
structure that's instantiated for this specific lambda.
This structure provides a static function with the signature matching the lambda's function signature.
Finally, the Adapter
structure is instantiated with this static function's pointer and implements the interface B that cannot_change
is expecting.
Please notice that:
CaptureLambda
static instance that must be set to the same lambda that is used to instantiate it. This means that there is no issue you could find with usual static variableAdapter
being instantiated for this CaptureLambda
specific instance, it can be completely optimized away by the compiler.std::function<>
in CaptureLambda
because I was lazy. A better solution would be to actually copy/move/ref the lambda to avoid the overhead of std::function
CaptureLambda
and use Adapter<decltype(f)>
directly in the cannot_change
call, since a non capturing lambda is convertible to a function pointer directly.