【问题标题】:Use of void template argument in early detection idiom implementation在早期检测成语实现中使用 void 模板参数
【发布时间】:2016-11-15 13:06:49
【问题描述】:

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>;

他们提到他们不喜欢这个,因为 voidis_assignable 特征中使用:

虽然生成的代码比 原来,我们不喜欢上面的检测接口,因为void 元函数调用中的参数是一个实现细节, 不应泄露给客户端代码。

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

如果我将is_assignable 重写为:

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

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

所以我的问题是我是否误解了本文档中的某些内容,或者只是一个错字?

如果它一个错字,那么我不明白为什么作者觉得有必要讨论他们不喜欢void泄露的原因,这让我很确定我是只是错过了一些东西。

【问题讨论】:

  • N4502 的前身N4436 有更严重的拼写错误(模板参数的数量不正确)。所以另一组错别字是不可能的...... ;-]
  • 不应该是using is_assignable = detect&lt;T, assign_t, void&gt;吗?
  • @Rerito 我不认为结尾的void 是必要的。正如 Ildjarn 所建议的那样,我认为造成混淆的根本原因是拼写错误。正如作者在论文脚注中提到的,也许这是一个“thinko”
  • @Rerito:谢谢。你能想出一个需要void 的场景吗?
  • 这种情况下的尾随 void 是不必要的,因为默认模板参数是在非专用模板中指定的。

标签: c++ language-lawyer template-meta-programming c++17


【解决方案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 变得很有必要,这样当Op&lt;Args...&gt; 是一个有效的表达式时,模板特化将匹配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。

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

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

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

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_tis_detected_v 实现中删除void,则选择主要专业化,我得到false (Example)。

这是因为void 存在以便匹配std::void_t&lt;Trait&lt;T, TraitArgs...&gt;&gt;,如果您还记得,如果模板参数格式正确,则void 的类型。如果模板参数格式不正确,则std::void_t&lt;Trait&lt;T, TraitArgs...&gt;&gt; 不是一个很好的匹配,它将恢复为默认的特化(false_type)。

当我们从调用中删除void(并简单地将TraitArgs... 留在原处)时,我们无法匹配true_type 特化中的std::void_t&lt;Trait&lt;T, TraitArgs...&gt;&gt; 参数。

还要注意,如果std::void_t&lt;Trait&lt;T, TraitArgs...&gt;&gt; 格式正确,它只是为主模板中的class... TraitArgs 参数提供void 类型,因此我们不需要定义额外的模板参数来接收@987654365 @。

总之,作者希望删除最终会出现在客户端代码中的void,因此本文后面的实现会稍微复杂一些。

感谢@Rerito 指出this answer,Yakk 还做了一些额外的工作以避免客户端代码中出现讨厌的void

【讨论】:

  • 如果这个人想要获得这样的批准,她/他不应该对其进行校对和测试吗?对读者来说非常烦人。
猜你喜欢
  • 2012-11-02
  • 2019-04-08
  • 1970-01-01
  • 2021-07-08
  • 1970-01-01
  • 2023-01-22
  • 2017-02-22
  • 1970-01-01
  • 2020-12-27
相关资源
最近更新 更多