在早期检测成语实现中使用 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

Although the resulting code was significantly more comprehensible than the original, we disliked the above detect interface because the void argument in the metafunction call is an implementation detail that shouldn’t leak out to client code.

但是,void 首先对我来说没有任何意义。如果我尝试使用此类型特征来检测 int 是否可复制分配,我会得到 std::false_type Demo.

如果我将 is_assignable 重写为:

template< class T >
using
is_assignable = detect<T, assign_t>;

这对我来说更有意义,然后特征似乎可以正常工作: Demo

所以我的问题是我是不是误解了这份文件中的某些内容,还是只是打字错误?

如果 打字错误,那么我不明白为什么作者觉得有必要讨论他们不喜欢 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 版本。

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_typefalse_type)给定一些 T 和我们的特征:

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

最后,我们还可以 "just check" 查看特征是否对某些提供的 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_tis_detected_v 实现中删除 void,则选择主要专业化,我得到 falseExample ).

这是因为 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