c++ - 在早期检测成语实现中使用 void 模板参数

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

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变得必要,以便模板特化将匹配 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;

请注意,我重命名为 OpTrait , ArgsTraitArgs , 并使用 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

如果我删除 void来 self 的 is_detected_tis_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在客户端代码中。

关于c++ - 在早期检测成语实现中使用 void 模板参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40610591/

相关文章:

c++ - 使用 auto&& 完美转发返回值

c++ - 构造函数初始值设定项列表未调用复制构造函数

C++11 类型的(有符号 + 无符号)?

c++ - 如何将编译时字符串 (BOOST_METAPARSE_STRING) 转换为运行时字符串?

行为类似于 __COUNTER__ 宏的 C++ 结构

c++ - 重新抛出异常保留回溯

c++ - 按 vector 中的对象属性合并排序

c++ - 如何在 C++ 中使用模板创建商业库?

c++ - const 类名 &obj == 类名 const &obj?

c++ - 如何解决 Qt 中的这个设计障碍?