在早期检测习语实现中使用 void 模板参数
Use of void template argument in early detection idiom implementation
在n4502中,作者描述了封装void_t
技巧的检测习语的早期实现。这是它的定义以及定义is_assignable
特征的用法(实际上它is_copy_assignable
template<class...>
using void_t = void;
// primary template handles all types not supporting the operation:
template< class, template<class> class, class = void_t< > >
struct
detect : std::false_type { };
// specialization recognizes/validates only types supporting the archetype:
template< class T, template<class> class Op >
struct
detect< T, Op, void_t<Op<T>> > : std::true_type { };
// archetypal expression for assignment operation:
template< class T >
using
assign_t = decltype( std::declval<T&>() = std::declval<T const &>() );
// trait corresponding to that archetype:
template< class T >
using
is_assignable = detect<void, assign_t, T>;
他们提到他们不喜欢这个,因为is_assignable
特征中使用的void
:
尽管生成的代码比 原来,我们不喜欢上面的检测界面,因为
void
元函数调用中的参数是一个实现细节 不应泄漏到客户端代码。
但是,首先,void
对我来说没有任何意义。如果我尝试使用此类型特征来检测int
是否可复制分配,我会得到std::false_type
演示。
如果我is_assignable
重写为:
template< class T >
using
is_assignable = detect<T, assign_t>;
这对我来说更有意义,那么该特征似乎可以正常工作:演示
所以我的问题是我是否误解了本文档中的某些内容,或者它只是一个错字?
如果这是一个错字,那么我不明白为什么作者觉得有必要讨论他们不喜欢void
泄漏,这让我很确定我只是错过了一些东西。
从作者如何编写他们的最终实现is_detected
来看,他们打算Op
是一个可变参数模板,它允许人们表达更多的概念:
(也来自n4502(
// primary template handles all types not supporting the archetypal Op:
template< class Default
, class // always void; supplied externally
, template<class...> class Op
, class... Args
>
struct
detector
{
using value_t = false_type;
using type = Default;
};
// the specialization recognizes and handles only types supporting Op:
template< class Default
, template<class...> class Op
, class... Args
>
struct
detector<Default, void_t<Op<Args...>>, Op, Args...>
{
using value_t = true_type;
using type = Op<Args...>;
};
//...
template< template<class...> class Op, class... Args >
using
is_detected = typename detector<void, void, Op, Args...>::value_t;
当您遇到这样的方案时,需要void
,以便在Op<Args...>
是有效表达式时,模板专用化将与true_type
版本匹配。
这是我对原始检测的调整:
// primary template handles all types not supporting the operation:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect : std::false_type { };
// specialization recognizes/validates only types supporting the archetype:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect< T, Trait, std::void_t<Trait<T, TraitArgs...>>, TraitArgs... > : std::true_type { };
template<class T, template<class...> class Trait, class... TraitArgs>
using is_detected_t = typename detect<T, Trait, void, TraitArgs...>::type;
template<class T, template<class...> class Trait, class... TraitArgs>
constexpr bool is_detected_v = detect<T, Trait, void, TraitArgs...>::value;
请注意,我将Op
重命名为Trait
,Args
重命名为TraitArgs
,并使用了std::void_t
,使其成为C++17。
现在,让我们定义一个特征来测试一个名为 Foo
的函数,该函数可以接受也可能不接受某些参数类型:
template<class T, class... Args>
using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...));
现在我们可以得到一个类型(true_type
或false_type
(,给定一些T
和我们的特征:
template< class T, class... Args>
using has_foo_t = is_detected_t<T, HasFoo_t, Args...>;
最后,我们还可以"只是检查"该特征是否对某些提供的T
和Args
有效:
template<class T, class... Args>
constexpr bool has_foo_v = is_detected_v<T, HasFoo_t, Args...>;
下面是开始测试的结构:
struct A
{
void Foo(int)
{
std::cout << "A::Foo(int)n";
}
};
最后是测试:
std::cout << std::boolalpha << has_foo_v<A, int> << std::endl; //true
std::cout << std::boolalpha << has_foo_v<A> << std::endl; // false
如果我从is_detected_t
中删除void
并is_detected_v
实现,则选择主要专用化,并且我得到false
(示例(。
这是因为void
在那里是为了匹配std::void_t<Trait<T, TraitArgs...>>
如果您还记得,如果模板参数格式正确,则会具有一种void
类型。如果模板参数的格式不正确,则std::void_t<Trait<T, TraitArgs...>>
不是很好的匹配,它将恢复为默认专用化 (false_type
(。
当我们从调用中删除void
(并简单地将TraitArgs...
保留在其位置(时,我们无法匹配true_type
专用化中的std::void_t<Trait<T, TraitArgs...>>
参数。
另请注意,如果std::void_t<Trait<T, TraitArgs...>>
格式正确,则它只是为主模板中的class... TraitArgs
参数提供void
类型,因此我们不需要定义额外的模板参数来接收void
。
总之,作者希望删除最终会出现在客户端代码中的void
,因此他们在本文后面的实现稍微复杂一些。
感谢@Rerito指出这个答案,Yakk还做了一些额外的工作来避免客户端代码中讨厌的void
。
- 在派生函数中指定void*参数
- void 函数可以传递值参数吗?
- 如何在C++中使用 Void 而不会收到有关参数的错误
- std::function<void()> 接受参数
- C++ 类型的参数与 void (__cdecl*)(void) 类型的参数不兼容,当调用 std::atexit()
- 是否将invoke_result与void参数类型一起使用
- 组合模板参数形成函数签名时无效的 void 参数
- 使用void* 参数定义纯虚函数的抽象基类.派生类匹配参数是指向某种类型的指针
- 为什么 void 参数不能存在于具有多个参数的函数中?
- Pthreads将void*参数强制转换为int数组
- C 函数声明对类型"void(*fcn)(void*)"参数的说明
- 从模拟函数调用中捕获 void* 参数
- 为void参数指定一个模板
- 如何在c++ OCX模块中指定一个接受void*参数的函数
- 某些 GL 函数的好奇"void*"参数
- 如何在C++中为多个对象类型使用void*参数
- 如何使void*参数在函数中保存其局部结果,该函数使用struct *调用
- 在需要void*参数的函数中传递unique_ptr最安全的方法是什么?
- 如何在类/函数模板中传递void参数
- 没有参数和C++中的void参数有什么区别