After further investigation, it turns out there exists a minor modification that can be performed to the next()
function, which makes the code work properly on clang++ versions above 7.0.0, but makes it stop working for all other clang++ versions.
Have a look at the following code, taken from my previous solution.
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
return R;
}
If you pay attention to it, what it literally does is to try to read the value associated with slot<N>
, add 1 to it and then associate this new value to the very same slot<N>
.
When slot<N>
has no associated value, the value associated with slot<Y>
is retrieved instead, with Y
being the highest index less than N
such that slot<Y>
has an associated value.
The problem with the above code is that, even though it works on g++, clang++ (rightfully, I would say?) makes reader(0, slot<N>())
permanently return whatever it returned when slot<N>
had no associated value. In turn, this means that all slots get effectively associated with the base value 0
.
The solution is to transform the above code into this one:
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N-1>())+1>::value) {
return R;
}
Notice that slot<N>()
has been modified into slot<N-1>()
. It makes sense: if I want to associate a value to slot<N>
, it means no value is associated yet, therefore it makes no sense to attempt to retrieve it. Also, we want to increase a counter, and value of the counter associated with slot<N>
has to be one plus the value associated with slot<N-1>
.
Eureka!
This breaks clang++ versions <= 7.0.0, though.
Conclusions
It seems to me that the original solution I posted has a conceptual bug, such that:
- g++ has quirk/bug/relaxation that cancels out with my solution’s bug and eventually makes the code work nonetheless.
- clang++ versions > 7.0.0 are stricter and don’t like the bug in the original code.
- clang++ versions <= 7.0.0 have a bug that makes the corrected solution not work.
Summing all that up, the following code works on all versions of g++ and clang++.
#if !defined(__clang_major__) || __clang_major__ > 7
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N-1>())+1>::value) {
return R;
}
#else
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
return R;
}
#endif
The code as-is also works with msvc.
The icc compiler doesn’t trigger SFINAE when using decltype(counter(slot<N>()))
, preferring to complain about not being able to deduce the return type of function "counter(slot<N>)"
because it has not been defined
. I believe this is a bug, that can be worked around by doing SFINAE on the direct result of counter(slot<N>)
. This works on all other compilers too, but g++ decides to spit out a copious amount of very annoying warnings that cannot be turned off. So, also in this case, #ifdef
could come to the rescue.
The proof is on godbolt, screnshotted below.