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

时间:2016-11-15 13:06:50

标签: 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>;

他们提到他们不喜欢这样,因为void特征中使用了is_assignable

  

虽然结果代码比...更易于理解   原来,我们不喜欢上面的检测界面,因为void   元函数调用中的参数是一个实现细节   不应泄露给客户端代码。

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

如果我将is_assignable重写为:

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

这对我来说更有意义,那么特质似乎正常工作: Demo

所以我的问题是我是否误解了本文档中的内容,还是仅仅是一个错字?

如果它 是一个错字,那么我不明白为什么作者觉得有必要讨论他们不喜欢void泄漏的问题,这让我非常肯定我我只是遗漏了一些东西。

1 个答案:

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

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

最后,我们也可以&#34;只需检查&#34;查看特征是否对某些提供的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

如果我从voidis_detected_t实施中移除is_detected_v,则会选择主要专业化,然后我会falseExample)。

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