在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>;
他们提到他们不喜欢这样,因为void
特征中使用了is_assignable
:
虽然结果代码比...更易于理解 原来,我们不喜欢上面的检测界面,因为
void
元函数调用中的参数是一个实现细节 不应泄露给客户端代码。
但是,void
首先对我没有任何意义。如果我尝试使用此类型特征来检测int
是否可以复制,我可以std::false_type
Demo。
如果我将is_assignable
重写为:
template< class T >
using
is_assignable = detect<T, assign_t>;
这对我来说更有意义,那么特质似乎正常工作: Demo
所以我的问题是我是否误解了本文档中的内容,还是仅仅是一个错字?
如果它 是一个错字,那么我不明白为什么作者觉得有必要讨论他们不喜欢void
泄漏的问题,这让我非常肯定我我只是遗漏了一些东西。
答案 0 :(得分:1)
判断作者如何编写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
变得必要,这样当true_type
是有效表达式时,模板专精化将与Op<Args...>
版本匹配。
Here's my tweak on the original detect to be variadic:
// 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...>;
最后,我们也可以&#34;只需检查&#34;查看特征是否对某些提供的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
如果我从void
和is_detected_t
实施中移除is_detected_v
,则会选择主要专业化,然后我会false
(Example)。
这是因为void
就是为了匹配std::void_t<Trait<T, TraitArgs...>>
,如果您回忆,如果模板参数格式正确,则其类型为void
。如果模板参数格式不正确,那么std::void_t<Trait<T, TraitArgs...>>
不是一个很好的匹配,它将恢复为默认的特化(false_type
)。
当我们从调用中删除void
时(只需将TraitArgs...
留在其位置),我们就无法匹配std::void_t<Trait<T, TraitArgs...>>
专精中的true_type
参数。
另请注意,如果std::void_t<Trait<T, TraitArgs...>>
格式正确,则只会为主模板中的void
参数提供class... TraitArgs
类型,因此我们不需要定义一个额外的模板参数,用于接收void
。
总之,作者希望删除最终会出现在客户端代码中的void
,因此本文后面的实现稍稍复杂一些。
感谢@Rerito指出this answer,其中Yakk还做了一些额外的工作,以避免客户端代码中令人讨厌的void
。