提问人:Quest 提问时间:8/14/2023 最后编辑:Jan SchultkeQuest 更新时间:8/14/2023 访问量:87
SFINAE 不会禁用其中一项功能
SFINAE does not disable one of the functions
问:
我有一个包装 a 并具有函数的类,一个如果不接受任何参数,另一个则使用。packaged_task
Callable
invoke()
Callable
我在访问第二个时遇到错误,即使它应该被 SFINAE 禁用。typename traits::template arg<0>::type
enable_if
知道我为什么会有这种行为吗?
#include <functional>
#include <type_traits>
namespace detail
{
template<typename T, typename Enabler = void>
struct function_traits;
template <typename T>
struct function_traits<T, typename std::enable_if<std::is_class<T>{}>::type> : public function_traits<decltype(&T::operator())>
{
};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
using result_type = ReturnType;
static constexpr auto n_args = sizeof...(Args);
template<std::size_t I>
struct arg { using type = typename std::tuple_element<I, std::tuple<Args...>>::type; };
template<std::size_t I>
using arg_t = typename arg<I>::type;
};
template <typename ReturnType, typename... Args>
struct function_traits<ReturnType(Args...)>
{
using result_type = ReturnType;
static constexpr auto n_args = sizeof...(Args);
template<std::size_t I>
struct arg { using type = typename std::tuple_element<I, std::tuple<Args...>>::type; };
template<std::size_t I>
using arg_t = typename arg<I>::type;
};
}
template<typename Callable>
class packaged_task
{
public:
using traits = detail::function_traits<Callable>;
template<std::size_t n_args = traits::n_args>
typename std::enable_if<n_args == 0>::type
invoke()
{
}
template<std::size_t n_args = traits::n_args>
typename std::enable_if<n_args == 1>::type
invoke(typename traits::template arg<0>::type val)
{
}
};
int main()
{
packaged_task<void()> task;
}
答:
3赞
Jan Schultke
8/14/2023
#1
允许编译器检查模板的有效性,甚至在实例化之前。要延迟此操作,必须使代码依赖于模板参数。问题出在这里:
// OK, delay validity check by making n_args depend on traits::n_args,
// which depends on the template parameter of the enclosing template
template<std::size_t n_args = traits::n_args>
// OK, this will do SFINAE based on the n_args template parameter to this function
typename std::enable_if<n_args == 1>::type
// ILL-FORMED!!, validity of arg<0> is checked BEFORE instantiation of this
// member function template
invoke(typename traits::template arg<0>::type val) { }
当类模板实例化时,就会变得已知,这意味着也可以进行诊断。然后,在调用函数之前执行有效性检查,并且代码无法编译。packaged_task
traits
traits::arg<0>
invoke
肮脏的解决方案
要解决此问题,您必须以某种方式依赖:arg<0>
n_args
template<std::size_t n_args = traits::n_args>
typename std::enable_if<n_args == 1>::type
invoke(typename traits::template arg<0 * n_args>::type val) { }
通过使用 中为零的乘法,表达式变得依赖于模板参数,并且有效性检查被延迟到函数模板的实例化。结果仍为零。0 * n_args
n_args
但是,此解决方案将信心置于编译器中,而不是诊断为零。可以说,该代码的格式仍然不正确,不需要诊断。它适用于每个主要的编译器,但不是很惯用,而且可能不正确。0 * n_args
清洁解决方案
// common base class for 0 args and 1 args which gets the traits
// of the function
template<typename Callable>
struct packaged_task_impl_base {
using traits = detail::function_traits<Callable>;
};
// partially specialized class template
template<typename Callable, std::size_t n_args>
struct packaged_task_impl;
// partial specialization for 0 args
template<typename Callable>
struct packaged_task_impl<Callable, 0>
: packaged_task_impl_base<Callable>
{
void invoke() { /* ... */ }
};
// partial specialization for 1 arg
template<typename Callable>
struct packaged_task_impl<Callable, 1>
: packaged_task_impl_base<Callable>
{
void invoke() { /* ... */ }
};
// wrapper which doesn't expose n_args in its template-head
template<typename Callable>
struct packaged_task
: private packaged_task_impl<Callable, detail::function_traits<Callable>::n_args>
{
using traits = typename packaged_task_impl<Callable,
detail::function_traits<Callable>::n_args>::traits;
// other stuff which doesn't depend on n_args here ...
};
这个解决方案乍一看可能看起来很长,但如果你觉得你可以将其内容复制并粘贴到每个部分专业化中,你至少可以删除。packaged_task_impl_base
packaged_task_impl
它还具有常规成员函数的优点,最后但并非最不重要的一点是,它绝对不会依赖于程序仍然可以说是格式错误的技巧。invoke()
评论
0赞
Quest
8/14/2023
这实际上很酷,我总是认为一旦你在成员函数模板中有一个依赖类型,整个事情就会延迟。
0赞
Quest
8/14/2023
我真的很喜欢你的清洁解决方案。最初,在知道问题出在哪里之前,我曾想过将其分成部分专业化,但想知道它是否足够干净,易于维护,但这看起来很整洁。
0赞
Yakk - Adam Nevraumont
8/15/2023
我自己会把 和 放入一个基于 CRTP 的基类中 - 的主体可以得到完整的类型并使用它。invoke()
invoke(one arg)
invoke
packaged_task
评论
result_type
decltype(fn())
packaged_task
template<class R, class...Args>class packaged_task<R(Args...)>
R invoke(Args...)