Including a hard error when creating an incorrect template in the SFINAE context in a soft error

Let's say we are given an instance of the template Container<U, Args...> (I think Container is std::vector ) and an asymmetric type T, and we need to check if we can call push_back on the object, enter Container<T> . Here is the code that uses the idiom of the detector:

 #include <iostream> #include <vector> #include <set> #include <string> #include <type_traits> #include <boost/iterator.hpp> #include <boost/range.hpp> template<typename, typename> struct replace { using type = struct Error; }; template<template<typename...> class Container, typename U, typename T> struct replace<Container<U>, T> { using type = Container<T>; }; template<typename Container, typename T> using replace_t = typename replace<Container, T>::type; template<typename Placeholder, template<typename...> class Op, typename... Args> struct isDetected : std::false_type {}; template<template<typename...> class Op, typename... Args> struct isDetected<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {}; template<typename Container, typename T> using pushBackDetector = decltype(std::declval<Container&>().push_back(std::declval<T>())); template<typename Container, typename T> bool canPushBack() { return isDetected<void, pushBackDetector, Container, T> {}; } int main() { std::cout << canPushBack<replace_t<std::vector<int>, double>, double>() << std::endl; std::cout << canPushBack<replace_t<std::set<int>, double>, double>() << std::endl; std::cout << canPushBack<replace_t<boost::iterator_range<std::string::iterator>, std::string::iterator>, double>() << std::endl; //std::cout << canPushBack<replace_t<boost::iterator_range<std::string::iterator>, int>, double>() << std::endl; } 

A tough example is available on Wandbox.

Indeed, it correctly infers that we can call push_back on std::vector<double> , but we cannot do this on std::set<double> or boost::iterator_range<std::string::iterator> .

Now let me check if we can call push_back on boost::iterator_range<int> and uncomment the last line! And now the code explodes so beautifully that I wonโ€™t give a complete error message here (itโ€™s better to do this with the examples above), but the essence of this is that the compiler tries to create an instance of boost::iterator_range<int> and turn the failure into enter some basic type of this into a hard error:

 /opt/wandbox/boost-1.65.1/clang-5.0.0/include/boost/iterator/iterator_categories.hpp:119:60: error: no type named 'iterator_category' in 'std::__1::iterator_traits<int>' typename boost::detail::iterator_traits<Iterator>::iterator_category ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~ /opt/wandbox/boost-1.65.1/clang-5.0.0/include/boost/range/iterator_range_core.hpp:156:32: note: in instantiation of template class 'boost::iterators::iterator_traversal<int>' requested here BOOST_DEDUCED_TYPENAME iterator_traversal<IteratorT>::type ^ /opt/wandbox/boost-1.65.1/clang-5.0.0/include/boost/range/iterator_range_core.hpp:436:67: note: in instantiation of template class 'boost::iterator_range_detail::pure_iterator_traversal<int>' requested here BOOST_DEDUCED_TYPENAME iterator_range_detail::pure_iterator_traversal<IteratorT>::type ^ prog.cc:31:61: note: in instantiation of template class 'boost::iterator_range<int>' requested here using pushBackDetector = decltype(std::declval<Container&>().push_back(std::declval<T>())); ^ prog.cc:28:31: note: in instantiation of template type alias 'pushBackDetector' requested here struct isDetected<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {}; ^ prog.cc:36:12: note: during template argument deduction for class template partial specialization 'isDetected<std::void_t<Op<Args...> >, Op, Args...>' [with Op = pushBackDetector, Args = <boost::iterator_range<int>, double>] return isDetected<void, pushBackDetector, Container, T> {}; ^ 

On the one hand, it makes sense - indeed, int not an iterator. On the other hand, it is highly desirable to catch this incorrect creation and simply return false from canPushBack() in this case. So, here is the question: is it possible to turn this hard error into a soft error and handle it gracefully?

+5
source share
1 answer

No, you cannot take a template that does not support SFINAE detection and make it SFINAE-friendly without manual work specific to the type in question, and sometimes this is not enough.

The best thing you can do is write a manual characteristic that does this for you, and an alias that SFINAE checks to see if it can be applied, and only returns the type if it can be.

What else, it is impossible to determine if something is an iterator. There are no standardized SFINAE-compatible X Iterator tests. As a rule, all iterators should support std::iterator_traits<T> , but there are zero requirements for neterators to generate a friendly SFINAE result when you pass them to std::iterator_traits , and in my experience passing void* to std::iterator_traits generates a friendly result not related to SFINAE.

You can try to hack one - to determine the various things that the iterator should do (be legible, incremental, equally comparable), but even there the type may not have errors related to SFINAE when it is tried. For example, take a non-uniformly comparable type and put it in std::vector , and trying to execute == might not compile with a hard error (at least the last time I checked).

Simple case:

 template<class T> struct problem { static_assert(!std::is_same<T,int>{}, "oh oh"); }; 

passing an int to a problem cannot be detected by SFINAE as a problem. If you create an instance of problem<int> , you get a hard error.

+6
source

Source: https://habr.com/ru/post/1273472/


All Articles