В прошлый раз, мы использовали SFINAE, чтобы понять, есть ли у типа определение, и мы использовали это в сочетании с if constexpr и универсальными лямбда-выражениями, чтобы код мог использовать тип, если он определен, при этом все еще принимаясь компилятором (и отбрасываясь) если тип не определен.

Однако в этом применении существует несколько проблем:

  • Каждый раз нужно писать struct.
  • Если тип не существует, то при присвоении ему имени этот тип вводится в текущее пространство имен, а не в пространство имен, в котором вы хотели, чтобы тип был.
  • Нужно использовать struct с неквалифицированным именем. Нельзя использовать его для проверки типа, который вы не импортировали в текущее пространство имен.

Мы можем исправить все три проблемы с помощью одного решения: предварительно объявить тип в нужном пространстве имен.



// awesome.h
namespace awesome
{
  // может или может не содержать
  struct special { ... };
}

// ваш код

namespace awesome
{
  // обеспечить объявление типов, которые мы детерминируем
  struct special;
}

После того, как вы это сделали, вам не нужно писать struct, потому что структура была объявлена. Использование ее в качестве параметра типа шаблона в call_if_defined не приведет к созданию нового объявления, поскольку все уже было объявлено. И так как она была объявлена, вы можете получить к ней доступ через ее неквалифицированное имя, ее полное имя пространства имен или через что-либо между ними. Также через псевдоним типа или зависимый тип (к сожалению, они не между).

namespace app
{
  void foo()
  {
    call_if_defined<awesome::special>([&](auto* p)
    {
       // Этот код компилируется только если "awesome::special"
       // определен. Создайте локальное имя для "special"
       // выведя его из фиктивного параметра.
       using special = std::decay_t<decltype(*p)>;

       // Теперь можно использовать локальное имя "special" для доступа
       // к параметрам "awesome::special".
       special::do_something();
    });
  }
}

Те, кто следил за данной серией статей с самого начала, могли заметить, что метод call_if_defined не совсем совпадает с версией, которую мы написали ранее. Новая версия поддерживает несколько параметров типа и вызывает лямбду, только если определены все типы.

Посмотрим ближе:

template<typename... T, typename TLambda>
void call_if_defined(TLambda&& lambda)
{
  if constexpr ((... && is_complete_type_v<T>)) {
    lambda(static_cast<T*>(nullptr)...);
  }
}

Двойные скобки в if constexpr ((...)) выглядят странно, но они обязательны. Внешние скобки требуются оператором if constexpr, а внутренние скобки требуются выражением свертки. Выражение свертки расширяется до

  if constexpr (
    (is_complete_type_v<T1> &&
     is_complete_type_v<T2> &&
     ...
     is_complete_type_v<Tn>))

Вызов лямбды использует расширение пакета параметров:

    lambda(static_cast<T*>(nullptr)...);

Это расширяется до

    lambda(static_cast<T1*>(nullptr),
           static_cast<T2*>(nullptr),
           ...,
           static_cast<Tn*>(nullptr));

где static_cast<T*>(nullptr) повторяется один раз для каждого типа.

Как я уже отмечал ранее, мы можем использовать эту функцию для вызова лямбды, если определены все типы:

void foo(Source const& source)
{
  call_if_defined<special, magic>(
    [&](auto* p1, auto* p2)
    {
      using special = std::decay_t<decltype(*p1)>;
      using magic = std::decay_t<decltype(*p2)>;

      auto s = source.try_get<special>();
      if (s) magic::add_magic(s);
    });
}

C++20 позволяет записать это так:

void foo(Source const& source)
{
  call_if_defined<special, magic>(
    [&]<typename special, typename magic>
    (special*, magic*)
    {
      auto s = source.try_get<special>();
      if (s) magic::add_magic(s);
    });
}

что позволяет вам называть тип шаблона, тем самым избавляя вас от необходимости заново извлекать его, играя с std::decay_t.

В следующей статье мы будем использовать это как трамплин и расширять схему.



Примечание: это четвертая часть основной серии статей, но еще есть и другие части (1,2,3,5). Для нетерпеливых: вот, что нужно скопировать и вставить:

template<typename, typename = void>
constexpr bool is_type_complete_v = false;

template<typename T>
constexpr bool is_type_complete_v
    <T, std::void_t<decltype(sizeof(T))>> = true;

template<typename... T, typename TLambda>
void call_if_defined(TLambda&& lambda)
{
  if constexpr ((... && is_complete_type_v<T>)) {
    lambda(static_cast<T*>(nullptr)...);
  }
}

Кстати, у нас есть классная вакансия


Более десяти лет Havok находится на острие инноваций в разработке игр и интерактивного 3D. Как часть Cognition, команды, отвечающей за HoloLens, мы теперь комбинируем эту экспертизу и силу облака Azure для разработки многих новых захватывающих сервисов, поддерживающих смешанную реальность. Среди них недавно анонсированная служба удаленного рендеринга Azure (Azure Remote Rendering). Мы увлечены совмещением AR, VR и облачных технологий, которые вместе позволяют создавать инновационные практики использования смешанной реальности.

Работа в Havok:

  • Вы будете работать в небольших командах с талантливыми разработчиками
  • У вас будет возможность работать с новыми технологиями на самых разных аппаратных платформах и устройствах
  • Вы будете работать над решением сложных технических задач, имеющих огромные перспективы
  • Вы будете сотрудничать с крутыми командами по всему миру

Обязанности


  • Проектировать, разрабатывать и тестировать высококачественный, эффективный и чистый мультиплатформенный С++ код
  • Разрабатывать хорошо-масштабируемые сервисы Azure
  • Работать напрямую с внутренними и внешними заказчиками, чтобы стимулировать разработку продукта

Квалификации


  • Навыки программирования и отладки на C++
  • Умение работать в команде с общим кодом
  • Опыт работы с облачными и распределенными сервисными технологиями (например, Azure Batch, Azure Blob Storage, Docker, Telemetry)

Будет плюсом


  • C#, ASP.Net, JavaScript, TypeScript, React
  • Unity, Unreal или связанные игровые движки
  • Опыт в интерактивном 3D, AR или VR
  • Сетевые и серверные сервисы
  • Оптимизация производительности

Вы можете узнать больше и подать свою заявку здесь или через LinkedIn.

Комментарии (3)


  1. kovserg
    19.07.2019 03:36
    -1

    Для тех кто не следил за серией статей. Какую задачу решает автор? Запутать следы в исходном коде?


  1. MooNDeaR
    19.07.2019 12:40

    Также через псевдоним типа или зависимый тип (к сожалению, они не между).

    Я перечитал эту фразу раза четыре, но так и не понял, что бы она могла значить)


    1. KanuTaH
      19.07.2019 19:45
      +1

      Ну имеется в виду, что если объявлен некий символ, обозначающий тип, то ты можешь получить к нему доступ по неквалифицированному имени (type_name), по полному имени с учетом всех namespace по пути (A::B::C::type_name), или в ряде случаев через что-то промежуточное (B::C::type_name). Еще можно объявить например type alias для этого типа через using или typedef, но это не будет «чем-то промежуточным». Шутка юмора типа.

      P.S. Лучше читать в оригинале, будет более понятно.