I have a templated class called sys, out of whose parameters there is a non-type uint64_t parameter (let's call it freq).
My sys class stores many objects of type bod - the bod class is also templated and accepts a uint64_t non-type template parameter that is also called freq.
The bod class has a specialization for the special case where freq == 0. In that case, the method method_A is NOT defined (but is for any non-zero freq (freq > 0)).
In one method in my sys class, I create a lambda that has a templated operator() method (note: not a templated lambda itself, i.e., where the anonymous ClosureType class would be templated; here only the operator() method is templated, using []<>(){} syntax).
The lambda is templated with 3 boolean (non-type) template parameters - the first one being M. The lambda captures this and does not accept any parameters.
Within the lambda, I loop through all the bod objects stored in my sys instance. I use, within the loop, an if constexpr (M && freq) statement to only call method_A on a bod object when M == true (M being the boolean template parameter that the lambda's operator() method is templated with) and when freq > 0 (freq being the sys class's uint64_t template parameter), since method_A would not be defined if freq == 0 (and I also only would want to call method_A if M == true for other reasons).
My question then is, why does the compiler give the error "no member named 'method_A' in 'bod<int, int, int, 0>'" (the ints being the the types the other template parameters were instantiated with)?
I ask because I am guarding against precisely that by including the if constexpr statement, to ensure that method_A is defined for the bod objects.
I am sure there is something going on with the order of template instantiations that I am missing here. Perhaps it has something to do with the operator() method of the lambda being templated, instead of the anonymous ClosureType itself? If so, is it because the anonymous ClosureType class is instantiated before the template instantation of its operator() method, making the compiler reach the call to method_A before it knows that it would never occur since it's guarded by the if constexpr statement?
To make the issue clearer, I provide a barebones implementation of the problem:
// primary template for "bod" class
template <typename A, typename B, typename C, uint64_t freq>
class bod {
// instance variables
// methods
void method_A () {
// funky stuff
}
};
// template specialization for the case of freq == 0
template <typename A, typename B, typename C>
class bod<A, B, C, 0> {
// instance variables
// methods
// NO method_A DEFINED WITHIN THE SPECIALIZATION
};
template <typename A, typename B, typename C, uint64_t freq>
class sys {
std::list<bod<A, B, C, freq>> list;
void problem_func() {
static auto lam = [this]<bool M, bool Y, bool Z>{
for (bod<A, B, C, freq>& b : list) {
// do stuff with b
if constexpr (M && freq) {
b.method_A(); // "no member named 'method_A'" error, even though...
} // ... the if constexpr block guarantees it to be defined
}
};
// use lam<> with combinations of true/false as the boolean template parameters
}
};
As I said, I'm almost certain it's got something to do with the ClosureType vs the operator() method being templated, but I'd love to hear from someone who can shed some light on it. Cheers.
I've reduced your example to a much simpler case, and now it looks like a clang bug.
template <bool freq> struct bod { void method_A () {} };
template <> struct bod<false> {};
template <bool freq>
void problem_func() {
auto lam = []<bool M>(){
bod<freq> b;
if constexpr (M && freq) {
b.method_A();
}
};
}
int main() {
problem_func<false>();
}
https://godbolt.org/z/9vYvv8v3x
What's interesting is that if you remove the (M && freq) condition, and put in a less constrained condition of just (freq), then it compiles.
So somehow, the dependency on the function template parameter causes clang to evaluate the body anyway, possibly because it takes a different path through handling it, since "freq" is known from the outside, but M is a dependent type. (When it only depends on freq, by the time it gets to the if constexpr it already knows that the condition is false. Clang seems to not realize that false and (dependent value X) is still always false, even if it doesn't know about X.)
Therefore, it seems to work if you change the layout of your code to utilize this insight:
Putting an outer if constexpr on freq, and then another if constexpr (M), the compiler is able to make the body "go away" when freq is 0, regardless of what M is.
if constexpr (freq) {
if constexpr (M) {
b.method_A();
}
}
Next problem, your lambda is hard to use since a user has to provide template parameters that are not deduced from the arguments. Maybe this is due to you making an example, but to demonstrate invoking the inner lambda the easiest way I found was to add an extra parameter so that the bool argument M could be deduced from a std::bool_constant.
This compiles and runs as I would expect, even on clang:
#include <iostream>
template <int freq> struct bod {
void method_A () { std::cout << "methodA called\n";}
};
template <> struct bod<false> {};
template <int freq>
void problem_func() {
auto lam = []<bool M>(std::bool_constant<M> arg){
bod<freq> b;
if constexpr (freq != 0) {
if constexpr (M) {
b.method_A();
}
}
};
std::cout << "lam called with true_type\n";
lam(std::true_type{});
std::cout << "lam called with false_type\n";
lam(std::false_type{});
}
int main() {
std::cout << "freq=0:\n";
problem_func<0>();
std::cout << "freq=1:\n";
problem_func<1>();
}
OUTPUT:
freq=0:
lam called with true_type
lam called with false_type
freq=1:
lam called with true_type
methodA called
lam called with false_type
https://godbolt.org/z/bv8P4dWhT
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With