Reputation: 2111
I was reading Scott Meyer's Effective Modern C++ and hit the item in which he's suggesting usage of lambda
s in place of std::function
and std::bind
. I understand his argument and his claim about drawbacks of std::function
and I agreed with him.
As of today, I decided to switch to templates for storing lambda
s (which do not need mutators). I do understand that type of every lambda
is only known to the compiler and even two identical lambda
s will have different types so how come the following code compiles and works just fine?
template<typename LambdaT>
class CaptureLambda
{
public:
CaptureLambda(const LambdaT& fn)
: mActionFn(fn) // initialize mActionFn to a user supplied lambda
{}
private:
LambdaT mActionFn{ []{} }; // initialize mActionFn to an empty lambda
};
My point of confusion is that, how come mActionFn
is default initiated to an empty lambda with a different type inside member declarations but the constructor of the class is happily accepting another type of lambda
in its arguments? are they cast
able to each other? if yes, why the following makes the compiler sad?
// Class stuff...
template<typename T>
void resetActionFn(const T& newFn) { // setter member
mActionFn = newFn;
}
// Class stuff...
Upvotes: 3
Views: 204
Reputation:
Non-static data member initialisers only get used if the constructor doesn't specify a different initialiser.
Given
struct S {
int i = 3;
S() : i(4) { }
};
you don't get a default constructor that first initialises i
to 3
, and then re-initialises it to 4
, you just get a constructor that initialises i
to 4
.
It's the same with your class. You don't have any constructor that doesn't initialise mActionFn
, so the initialiser doesn't ever get used.
Now, as Piotr S. points out in the comments, in general, the intialiser must still be semantically valid, but your data member has a dependent type, so the validity cannot be checked at template definition time, and the initialiser never gets instantiated since it is unused, so the error goes undetected at instantiation time as well. A similar simpler example is
template <typename T>
struct S {
T x = 3;
S() : x(0) { }
};
int main() {
S<void*>();
}
which is accepted silently by GCC even though 3
is an invalid initialiser for a field of type void*
. clang rejects it though. The standard is unclear on whether the instantiation of a template causes the instantiation of any unused NSDMIs, and some compilers instantiate them only as needed. There is agreement that they should be instantiated only as needed, but there are some problems with that approach, which is why not all compilers implement that yet.
Upvotes: 4