3 ms·
I'm not saying that you are making things SFINAE friendly. I'm just curious about a use case you have where you use enable_if on completeness. Not asking for ma
by steerablesafe 5y ago
I'm not saying that you are making things SFINAE friendly. I'm just curious about a use case you have where you use enable_if on completeness. Not asking for making up a use case where you SFINAE on completeness.
> which would avoid putting (void)sizeof(It) directly inside an SFINAE context. I'm just saying you could do it this way without violating ODR or other stuff I think (unless I'm missing something).
Putting it in an alias template doesn't sidestep putting it in an SFINAE context. The detection idiom works because it can still be an SFINAE context.
- dataflow 5y ago> I'm not saying that you are making things SFINAE friendly. I thought that's why you wrote "static_assert instead of trying to be SFINAE-friendly" then, but okay. > I'm just curious about a use case you have where you use enable_if on completeness. That's all you want, without SFINAE? How about something like this: template<class T> class S { std::unique_ptr<unsigned char[]> buf; public: template<std::enable_if_t<((void)sizeof(T), true), bool> = false> explicit S(unsigned char *p, size_t n) : buf(p) { assert(sizeof(T) <= n); } // ... }; NDEBUG optimizes out the completeness check that's implicit in the assert() call, but you'd still want to ensure completeness even without the assert, so you write it explicitly. Obviously you can use static_assert here too, but merely using enable_if in lieu of that doesn't suddenly get you an ODR violation. EDIT: Come to think of it, actually I think static_assert here is less safe than enable_if and increases the risk of an ODR violation. Check out the difference in behavior here when you uncomment the enable_if_t: #include <type_traits> class X; template<class T> class S { public: // template<std::enable_if_t<((void)sizeof(T), true), bool> = false> explicit S(unsigned char *) { static_assert(sizeof(T) > 0, "oops"); } }; static S<X> s(nullptr); int main() { } class X { };
- steerablesafe 5y agoYes, that's still SFINAE. Now `is_constructible_v<S<X>>` evaluates to either `true` or `false`, depending on `X` being complete or not on the point of instantiation, while with the static assert it's always `true`, but you get an error at the point where the default constructor is instantiated. The different value for `is_constructible_v<S<X>>` can cause ODR violation in this variable template itself https://eel.is/c++draft/meta#rqmts-5 https://eel.is/c++draft/meta#rqmts-5, or otherwise further down the line. About your edit: I didn't know about that issue. Apparently it is ODR violation and ill-formed, no diagnostic required according to https://eel.is/c++draft/temp.point#7 https://eel.is/c++draft/temp.point#7 :(. I'm not happy about it. Having said that, yours is not free from ODR violation issues.
- dataflow 5y ago> Yes, that's still SFINAE I saw it as "SFIAE" (no N) given there's no other overloads and thus you would get an error, but sure, I guess syntactically it's in an SFIANE context. > ODR violation in this variable template itself https://eel.is/c++draft/meta#rqmts-5 https://eel.is/c++draft/meta#rqmts-5, or otherwise further down the line. [...] Having said that, yours is not free from ODR violation issues. I'm not passing anything incomplete to something in <type_trait> anywhere in this code though. In fact I don't even see where it says that such UB (even if you were to deliberately pass it to is_constructible) would be an ODR violation specifically. Note the claim wasn't "it is impossible to write undefined behavior by using this snippet". The claim was just there's no ODR violation in that code. If your goal was to introduce undefined behavior in general there are about a million ways you can already do that even without this. This is just about the most benign kind of UB you can have in reality; I'm pretty darn sure no compiler is going to deliberately generate a buggy program with this snippet merely because the type was technically incomplete there. Worst case is you end up calling the wrong constructor, but you're the one writing the constructors, so you wouldn't be introducing potentially-conflicting overloads in the first place. And even in that case you would really have to deliberately go out of your way to write unrealistic code to get back a buggy program (i.e. such that the alternative constructor actually ends up doing the wrong thing). I know I've never seen this blow up in actual code. > About your edit: I didn't know about that issue. Apparently it is ODR violation and ill-formed, no diagnostic required according to https://eel.is/c++draft/temp.point#7 https://eel.is/c++draft/temp.point#7 :(. I'm not happy about it. I dare say that situation is a lot more likely to cause problems in reality than the UB you're worried about, given forward declarations + subsequent accidental name collisions are things I've written in the past. If anything I would want to mitigate against this, not that.
- steerablesafe 5y agoThere is no ODR violation in this itself either: template<class T> class S { public: // template<std::enable_if_t<((void)sizeof(T), true), bool> = false> explicit S(unsigned char *) { static_assert(sizeof(T) > 0, "oops"); } }; It was always about usage. Some constructs are easier to misuse than others.