Простейшая реализация "облегченной идиомы категоризации типа"? - PullRequest
0 голосов
/ 12 октября 2018

Моя цель состоит в том, чтобы реализовать предикат, который обнаруживает присутствие вложенного псевдонима 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 назвал это «облегченной категоризацией типа» и предоставил решение в этом ответе .Я не смог найти никаких ссылок на идиому с таким именем (знаете ли вы что-нибудь?) Вот реализация Джоэля 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?Какую версию я предпочитаю?

Насколько я понимаю, версия Джоэла позволила бы my_tag псевдонимам любого типа, тогда как моей версии требуется my_tag для псевдонима void.Но учитывая цель маркировки типов для тестирования легких предикатов, мне не ясно, какая версия предпочтительнее.

Дополнительные вопросы: Кроме того, есть ли другие названия для этой идиомы?Используется ли он в каких-либо библиотеках, которые я мог бы исследовать?Пока что я не нашел имени, которое бы отображало результаты поиска.

Ответы [ 2 ]

0 голосов
/ 12 октября 2018

Во-первых, ваш работает, но зависит от того, является ли он void.Этот облегченный тег может быть полезен, если в некоторых ситуациях он имеет тип, отличный от void, и если он не является 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 >();

, который возвращает компиляцию-time true или false, если double имеет тег.

Мы можем решить, что 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 пример .

0 голосов
/ 12 октября 2018

Для вашей настройки нет никакой разницы между оригинальной версией и вашей.Оба используют SFINAE, чтобы выбрать правильный has_my_tag.Однако ваша версия ограничивает ваш typedef/using до my_tag=void.если my_tag имеет typedef'd как любой другой тип, ваша специализация не будет совпадать, и вы в конечном итоге создадите экземпляр основного шаблона, что можно увидеть здесь .

Причина этого в том, что когда вы создаете шаблоны в main, static_assert(has_my_tag<A>::value, ""); вы не указываете второй параметр, поэтому используется значение по умолчанию (void), то есть has_my_tag<A,void>::value

Ваша специализация должна соответствовать этому, чтобы это учитывалось.

Использование enable_if_type (в основном выполняющее работу void_t в c ++ 17) - это включение SFINAE на ::type член T, но всегда приводит к пустоте, так что ваша специализация будет соответствовать, когда существует ::type, независимо от типа my_tag typedef.

Это позволяет вам просто беспокоиться о том,существует, а не его тип;

Лично я бы использовал подход, который не зависит от того, является ли my_type typedef'd как 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;
}

Демо

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...