3 ms·
Do you `enable_if` on completeness of a type? I would recommend against it, it's prone to ODR violations.
by steerablesafe 5y ago
Do you `enable_if` on completeness of a type? I would recommend against it, it's prone to ODR violations.
- dataflow 5y agoNot in functions I believe (that was just for illustration), but in other occasions, yes, it's happened - sometimes it's necessary with template metaprogramming.
- steerablesafe 5y agoI'm curious about a valid use case there. If you have a minimal example, or a link to some public code, I would love to see it. There is a reason to be cautious there, the standard library also tends to static_assert on completeness instead of trying to be SFINAE-friendly.
- dataflow 5y agoI never said I make it SFINAE-friendly! I just said I use enable_if. Sometimes I use enable_if because it's handier for me than an alternative like static_assert, that's all. I actually don't recall if I've ever needed this in an SFINAE context before. That said, here's one possible place where it might make sense with SFINAE. Note that I haven't fully thought this through, I'm just doing this off the top of my head right now, so it's possible I'll miss something: #include <iterator> #include <type_traits> namespace stdext { template<class> class checked_array_iterator; } // Forward-declare MSVC-specific type namespace bar { namespace { template<class T> std::enable_if_t<(std::is_same_v<stdext::checked_array_iterator<typename std::iterator_traits<typename T::foo_iterator>::value_type *>, typename T::foo_iterator> && ((void)sizeof(typename T::foo_iterator), true))> debug_verify(const T &obj, ...) { // extra sanity checks against obj.get_foo_iterator() } template<class T> std::enable_if_t<!(std::is_same_v<stdext::checked_array_iterator<typename std::iterator_traits<typename T::foo_iterator>::value_type *>, typename T::foo_iterator> && ((void)sizeof(typename T::foo_iterator), true))> debug_verify(const T &) { // nothing to do } } class S { auto foo() { bar::debug_verify(this->field1); bar::debug_verify(this->field2); } // ... }; } Note that I'm by no means claiming this is the only way to do this. (Edit: Removed a final comment about turning it into a is_checked_array_iterator trait - messed up that part.)
- steerablesafe 5y agoI'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.