在早期检测习语实现中使用 void 模板参数

Use of void template argument in early detection idiom implementation

本文关键字:void 参数 检测 习语 实现      更新时间:2023-10-16

在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重命名为TraitArgs重命名为TraitArgs,并使用了std::void_t,使其成为C++17。

现在,让我们定义一个特征来测试一个名为 Foo 的函数,该函数可以接受也可能不接受某些参数类型:

template<class T, class... Args>
using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...));

现在我们可以得到一个类型(true_typefalse_type(,给定一些T和我们的特征:

template< class T, class... Args>
using has_foo_t = is_detected_t<T, HasFoo_t, Args...>;

最后,我们还可以"只是检查"该特征是否对某些提供的TArgs有效:

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中删除voidis_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