使用 std::result_of 的 SFINAE 意外失败
Unexpected SFINAE failure using std::result_of
在 c++14 中,如果表达式格式错误*,std::result_of 应该导致 SFINAE。相反,我在下面的最后一个案例中遇到了编译错误 ("invalid operands to binary expression")(即让编译器推断 std::plus<> 的类型)。前三个案例按预期工作。代码和结果如下所示。
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/apply.hpp>
#include <iostream>
#include <utility>
#include <stdexcept>
#include <functional>
namespace mpl = boost::mpl;
template <typename OP, typename T, typename OP_T = typename mpl::apply<OP, T>::type>
struct apply_if_ok: OP_T {
template <typename...Args, typename R = std::result_of_t<OP_T(Args...)>>
R operator()(Args&&...args) const {
return static_cast<OP_T>(*this)(std::forward<Args>(args)...);
}
template <typename...Args>
auto operator()(...) const {
// throw std::runtime_error("Invalid arguments");
return "Invalid arguments";
}
};
int main() {
using OP = std::plus<mpl::_>;
int i = 3;
auto n1 = apply_if_ok<OP, void>()(1, 2);
std::cout << "plus (1, 2) = " << n1 << std::endl;
auto n2 = apply_if_ok<OP, void>()(1, &i);
std::cout << "plus (1, *) = " << n2 << std::endl;
auto n3 = apply_if_ok<OP, int>()(&i, &i);
std::cout << "plus (*, *) = " << n3 << std::endl;
// auto n4 = apply_if_ok<OP, void>()(&i, &i);
// std::cout << "plus (*, *) = " << n4 << std::endl;
}
输出:
% c++ -std=c++1y -g -pedantic sfinae_result_of.cc -o sfinae_result_of
./sfinae_result_of
plus (1, 2) = 3
plus (1, *) = 0x7fff5e782a80
plus (*, *) = Invalid arguments
% c++ -v
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix
如能指出我做错的地方,我们将不胜感激!
谢谢。
- 来自 cppreference.com。我认为相关的标准参考是 20.10.7.6,对最后一个 table 条目的评论。
这是由后备箱中的 bug in libc++, which I actually just reported a few days ago. (Update: The bug has been fixed 引起的。)
问题是他们的 "diamond functor" 实施不符合标准。例如,他们实现 std::plus<void>::operator()
如下:
template <class _T1, class _T2>
_LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
auto operator()(_T1&& __t, _T2&& __u) const
{ return _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u); }
什么时候应该
template <class _T1, class _T2>
_LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
auto operator()(_T1&& __t, _T2&& __u) const
-> decltype(_VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u))
{ return _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u); }
缺失的trailing-return-type意味着两件事:
- 他们不再是"perfectly returning";相反,return 类型是使用
auto
的规则推导出来的,本质上导致它被衰减。尾随 return 类型,当其中的表达式格式正确时,等同于 returning decltype(auto)
.
- SFINAE 不再适用于表达式
_VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u)
。在无错误的实现中,operator()
声明将从重载集中删除,重载解析将失败,然后 std::result_of
将发挥其 SFINAE 友好的魔力。相反,函数声明被成功实例化,由重载决议选择,然后当编译器试图实例化主体以实际推导 return 类型时发生硬错误。
您的问题是由 #2 引起的。
在 c++14 中,如果表达式格式错误*,std::result_of 应该导致 SFINAE。相反,我在下面的最后一个案例中遇到了编译错误 ("invalid operands to binary expression")(即让编译器推断 std::plus<> 的类型)。前三个案例按预期工作。代码和结果如下所示。
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/apply.hpp>
#include <iostream>
#include <utility>
#include <stdexcept>
#include <functional>
namespace mpl = boost::mpl;
template <typename OP, typename T, typename OP_T = typename mpl::apply<OP, T>::type>
struct apply_if_ok: OP_T {
template <typename...Args, typename R = std::result_of_t<OP_T(Args...)>>
R operator()(Args&&...args) const {
return static_cast<OP_T>(*this)(std::forward<Args>(args)...);
}
template <typename...Args>
auto operator()(...) const {
// throw std::runtime_error("Invalid arguments");
return "Invalid arguments";
}
};
int main() {
using OP = std::plus<mpl::_>;
int i = 3;
auto n1 = apply_if_ok<OP, void>()(1, 2);
std::cout << "plus (1, 2) = " << n1 << std::endl;
auto n2 = apply_if_ok<OP, void>()(1, &i);
std::cout << "plus (1, *) = " << n2 << std::endl;
auto n3 = apply_if_ok<OP, int>()(&i, &i);
std::cout << "plus (*, *) = " << n3 << std::endl;
// auto n4 = apply_if_ok<OP, void>()(&i, &i);
// std::cout << "plus (*, *) = " << n4 << std::endl;
}
输出:
% c++ -std=c++1y -g -pedantic sfinae_result_of.cc -o sfinae_result_of
./sfinae_result_of
plus (1, 2) = 3
plus (1, *) = 0x7fff5e782a80
plus (*, *) = Invalid arguments
% c++ -v
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix
如能指出我做错的地方,我们将不胜感激!
谢谢。
- 来自 cppreference.com。我认为相关的标准参考是 20.10.7.6,对最后一个 table 条目的评论。
这是由后备箱中的 bug in libc++, which I actually just reported a few days ago. (Update: The bug has been fixed 引起的。)
问题是他们的 "diamond functor" 实施不符合标准。例如,他们实现 std::plus<void>::operator()
如下:
template <class _T1, class _T2>
_LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
auto operator()(_T1&& __t, _T2&& __u) const
{ return _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u); }
什么时候应该
template <class _T1, class _T2>
_LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
auto operator()(_T1&& __t, _T2&& __u) const
-> decltype(_VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u))
{ return _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u); }
缺失的trailing-return-type意味着两件事:
- 他们不再是"perfectly returning";相反,return 类型是使用
auto
的规则推导出来的,本质上导致它被衰减。尾随 return 类型,当其中的表达式格式正确时,等同于 returningdecltype(auto)
. - SFINAE 不再适用于表达式
_VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u)
。在无错误的实现中,operator()
声明将从重载集中删除,重载解析将失败,然后std::result_of
将发挥其 SFINAE 友好的魔力。相反,函数声明被成功实例化,由重载决议选择,然后当编译器试图实例化主体以实际推导 return 类型时发生硬错误。
您的问题是由 #2 引起的。