【发布时间】:2017-11-17 14:24:29
【问题描述】:
假设我们有一个模板实例化Container<U, Args...>(认为Container 是一个std::vector)和一个非模板类型T,我们需要检查我们是否可以在一个对象上调用push_back输入Container<T>。以下是使用检测器习语的代码:
#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;
}
Wandbox 的 available 就是一个活生生的例子。
确实,它正确推断出我们可以在std::vector<double> 上调用push_back,但我们不能在std::set<double> 或boost::iterator_range<std::string::iterator> 上这样做。
现在让我们检查是否可以在boost::iterator_range<int> 上调用push_back 并取消注释最后一行!现在代码爆炸得非常漂亮,我不会在这里给出完整的错误消息(最好在上面链接的实时示例中这样做),但它的要点是编译器试图实例化 boost::iterator_range<int> 并将失败转为将某个基类型实例化为硬错误:
/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> {};
^
一方面,这完全有道理——确实,int 不是迭代器。另一方面,非常希望捕获这个不正确的实例化并在这种情况下从canPushBack() 返回false。那么,问题来了:能不能把这个硬错误变成软错误,优雅地处理呢?
【问题讨论】:
-
“硬错误”“软错误”是什么?
-
错误发生在
boost::iterator_range<int>,而不是canPushBack。 -
@jarod42,OP 知道;我认为他希望在 Container
不是“有效”时检测 push_back() 有效性失败。如果没有 Container 的帮助,我认为这是不可能的 ... 因为我认为 std::declval &>().push_back 总是需要 Container 来隐式实例化 ... -
我认为问题出在 replace/replace_t - 如果不是有效类型,则不实例化 Container
(改为提供 struct Errror )。但是不知道能不能实现... -
看起来
boost::iterator_range对 SFINAE 不友好;我们无法制作出可靠的is_constructible特征。
标签: c++ c++11 templates sfinae