【问题标题】:Turning a hard error on incorrect template instantiation in SFINAE context into a soft error将 SFINAE 上下文中不正确模板实例化的硬错误转换为软错误
【发布时间】: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&lt;double&gt; 上调用push_back,但我们不能在std::set&lt;double&gt;boost::iterator_range&lt;std::string::iterator&gt; 上这样做。

现在让我们检查是否可以在boost::iterator_range&lt;int&gt; 上调用push_back 并取消注释最后一行!现在代码爆炸得非常漂亮,我不会在这里给出完整的错误消息(最好在上面链接的实时示例中这样做),但它的要点是编译器试图实例化 boost::iterator_range&lt;int&gt; 并将失败转为将某个基类型实例化为硬错误:

/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&lt;int&gt;,而不是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


【解决方案1】:

不,您不能使用不支持 SFINAE 检测的模板并使其对 SFINAE 友好,而无需针对相关类型进行手动工作,有时这还不够。

您能做的最好的事情就是编写一个手动 trait 来为您做这件事,以及一个 SFINAE 检查它是否可以应用的别名,如果可以的话,只返回一个类型。

更重要的是,无法检测某物是否为迭代器。没有标准强制要求 SFINAE 友好的“X 是否是迭代器”测试。作为一般规则,所有迭代器都必须支持std::iterator_traits&lt;T&gt;,但有要求非迭代器在将它们传递给std::iterator_traits 时必须生成对SFINAE 友好的结果,并且根据我传递@ 的经验987654323@ 到 std::iterator_traits 生成非 SFINAE 友好的结果。

您可以尝试破解一个 - 检测迭代器必须做的各种事情(可取消引用、可递增、同样可比较),但即使有一个类型在您尝试时也可能没有 SFINAE 友好的错误。例如,取非等可比较类型并将其放入std::vector,尝试执行== 可能无法编译并出现硬错误(至少在我上次检查时)。

一个简单的例子是:

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

int 传递给problem 不能被 SFINAE 检测为问题。如果你实例化problem&lt;int&gt;,你会得到一个硬错误。

【讨论】:

  • 对,所以这意味着我最担心的事情是真的:我必须检查它是否是boost::iterator_range(或者将来可能需要类似的东西)在我的特定案子。谢谢!
  • @0xd34df00d 要求传递模板的人 in 提供 SFINAE 友好版本,如果他们希望它以 SFINAE 友好的方式工作。例如,template&lt;class T&gt; using my_iterator_range = apply_if&lt; my_is_iterator&lt;T&gt;{}, boost::iterator_range, T &gt;;my_iterator_range 现在变为boost::iterator_range 的 SFINAE 友好版本。不是拼凑,而是元编程代码的输入要求。
猜你喜欢
  • 2016-07-23
  • 1970-01-01
  • 2019-11-17
  • 2014-04-20
  • 1970-01-01
  • 2018-12-03
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多