最简单的“轻量级分类习惯”实现?

时间:2018-10-12 10:44:21

标签: c++ c++11

我的目标是实现一个谓词,该谓词可检测嵌套using别名(或typedef)的存在,该别名充当轻型标签以指示类具有某些属性(对于通用编程的目的)。例如,has_my_tag<T>谓词的行为应如下:

struct A {
  using my_tag = void;
};

struct B {};

int main()
{
    static_assert(has_my_tag<A>::value, "");  // evaluate to true if my_tag=void is present
    static_assert(!has_my_tag<B>::value, ""); // false otherwise
}

用户@JoelFalcou将此称为“轻量级分类习惯用法”,并在this answer中提供了一种解决方案。我一直找不到该名称的成语的引用(您知道吗?)这是Joel对has_my_tag<>的实现:

template<class T, class R = void>  
struct enable_if_type { typedef R type; };

template<class T, class Enable = void>
struct has_my_tag : std::false_type {};

template<class T>
struct has_my_tag<T, typename enable_if_type<typename T::my_tag>::type> : 
std::true_type
{};

这是编译器资源管理器上的有效版本:https://godbolt.org/z/EEOBb-

我想出了以下简化版本:

template<class T, class Enable = void>
struct has_my_tag : std::false_type {};

template<class T>
struct has_my_tag<T, typename T::my_tag> : std::true_type
{};

https://godbolt.org/z/yhkHp7

我的问题:简化版本是否可以实现习惯用法?在某些情况下会失败吗?有没有更简单的版本可以在C ++ 11中使用?我应该选择哪个版本?

据我了解,Joel的版本将允许my_tag为任何类型加上别名,而我的版本则要求my_tagvoid加上别名。但是考虑到为轻量级谓词测试标记类型的目标,我不清楚哪个版本是首选。

辅助问题:另外,此成语还有其他名称吗?我可以研究的任何图书馆中都使用过它吗?到目前为止,我还没有找到能显示任何搜索结果的名称。

2 个答案:

答案 0 :(得分:7)

对于您的设置,原始版本与您的版本没有区别。两者都使用SFINAE选择正确的has_my_tag。但是,您的版本确实将typedef/using限制为my_tag=void。如果将my_tag与其他任何类型进行类型定义,则您的专业化将不匹配,并且最终将实例化主模板,如here所示。

这样做的原因是,在实例化main static_assert(has_my_tag<A>::value, "");中的模板时,您没有指定第二个参数,因此使用默认值(void),即has_my_tag<A,void>::value

您的专业必须与之匹配才能被考虑。

使用enable_if_type(在c ++ 17中基本上完成void_t的工作)的目的是在T的::type成员上启用SFINAE,但始终导致无效,这样无论::type typedef的类型如何,当my_tag存在时,您的专业将匹配。

这使您可以担心它是否存在,而不是它的类型;

我个人将使用不依赖于my_type被类型化为void(enable_if_type版本或类似...)的方法。

#include <iostream>
#include <type_traits>

struct A {
  using my_tag = void;
};

struct B {};

struct C {
  using my_tag = int; // with void_t also works with my_tag = int
};

struct D {
  struct my_tag{}; //or some struct as the tag
};

// same as your enable_if_type
template <typename...>
using void_t = void;


template<class T, class Enable = void>
struct has_my_tag : std::false_type {};

template<class T>
struct has_my_tag<T, void_t<typename T::my_tag>> : std::true_type
{};


int main() {
    std::cout << has_my_tag<A>::value << std::endl;
    std::cout << has_my_tag<B>::value << std::endl;
    std::cout << has_my_tag<C>::value << std::endl;
    std::cout << has_my_tag<D>::value << std::endl;
    return 0;
}

Demo

答案 1 :(得分:3)

首先,您的工作正常,但确实取决于它是void。如果轻量级标签在某些情况下带有非空类型,则该标签可能很有用;如果非空,则您会静默地检测失败,这似乎很糟糕。

第二,您的类型标记要求要求您修改类型,这意味着您无法获得内置的或您不拥有的类型(例如std中的类型)。我们可以解决这个问题。

namespace type_tag {

  namespace adl {
    template<template<class...>class tag>
    struct tag_token_t {};

    template<class T, template<class...> class tag>
    constexpr
    decltype( (void)(std::declval<tag<T>>()), std::true_type{} )
    tag_test( T*, tag_token_t<tag> ) { return {}; }

    template<class T, template<class...> class tag, class...LowPriority>
    constexpr
    std::enable_if_t<!std::is_same<T,int>{}, std::false_type> tag_test(T*, tag_token_t<tag>, LowPriority&&...) {
      return {};
    }
  }
  template<template<class...>class Z>using tag_token_t = adl::tag_token_t<Z>;

  template<template<class...>class tag>
  constexpr tag_token_t<tag> tag_token{};

  namespace details {
    template<class T, template<class...>class tag>
    constexpr auto test_impl( T*, tag_token_t<tag> ) {
      return tag_test( (T*)nullptr, tag_token<tag> );
    }
  }
  template<class T, template<class>class tag>
  constexpr auto tag_test() {
    return details::test_impl((T*)nullptr, tag_token<tag>);
  }
}

所以现在这是一个标签:

template<class T>
using my_tag = typename T::my_tag;

我们可以对其进行如下测试:

constexpr auto double_has_tag = type_tag::tag_test< double, my_tag >();

如果double有标签,则返回编译时为true或false。

我们可以通过执行以下操作来确定int具有标签:

namespace type_tag::adl {
  constexpr std::true_type tag_test( int*, type_tag::tag_token_t<my_tag> ) {
    return {};
  }
}

或者,对于我们控制的类型:

struct my_tagged_type {
  using my_tag = void;
};

对于类型,我们可以将名称注入其名称空间(即,不是std或内置类型),我们可以这样做:

namespace not_my_ns {
  constexpr std::true_type tag_test( not_my_type*, ::tag_test::tag_token_t<::my_tag> ) {
    return {};
  }
}

突然type_tag::tag_test<not_my_ns::not_my_type, ::my_tag>()是真的。

一旦有了tag_test< type, tag_name >(),我们就可以使用通常的std::enable_if来代替某些自定义系统。

该系统的优点包括:

  1. 可以扩展它,而无需更改要标记的类型。

  2. 可以使用系统所使用的using tag=void;using tag=int;对其进行扩展。

  3. 在SFINAE的使用点,它只是另一个编译时bool。因此,您现有的SFINAE模式就可以使用它。

  4. 如果您出于不相关的原因在他人使用的结构中为标记类型选择了一个较差的名称,tag_test可以按类型覆盖此标记。

缺点是这样做花了一些魔法。但是在常见的用例中,最终用户所需的工作与轻量级系统相同。在更复杂的用例中,这使您可以执行轻量级应用程序无法执行的操作。

Live example