Как исправить ранее работающую добавленную в шаблон функцию друга? - PullRequest
3 голосов
/ 28 апреля 2019

Я недавно обновил компилятор gcc с версии 5 до 8, и он сломал наш производственный код. Упрощенная версия сломанного кода включена ниже:

#include <utility>

// Imagine this has several template parameters not just Id and
// this class provides lots of friend functions for retrieving
// all this "metadata". Just one is implemented in this example.
template <typename Tag, unsigned Id>
class MetadataImpl
  {
  template <typename T, typename U>
  using matches =
    typename std::enable_if<std::is_same<T, U>::value>::type;

  template <typename _Tag, typename = matches<_Tag, Tag>>
  friend unsigned GetId(Tag* = nullptr)
    { return Id; }
  };

// Let's generate some instances...
template class MetadataImpl<int, 1>;
template class MetadataImpl<double, 2>;

// And a simple test function.
unsigned test()
  {
  return GetId<int>();
  }

Проще говоря, этот код обеспечивает способ захвата метаданных вокруг тега (тип в приведенном выше примере, но также может быть значением enum) и был первоначально закодирован около 10+ лет назад и видел много gcc обновления, но что-то «сломалось» в gcc 6 (проверено с помощью известного онлайн-компилятора godbolt).

Вполне возможно, что этот код не был поддержан стандартом c ++ и был просто расширением gcc, которое теперь было удалено, но мне было бы интересно узнать, так ли это на самом деле и для чего может быть обоснование это отклонено стандартом.

Кажется также, что clang также не поддерживает этот код, но я заметил, что если вы выполняете ast-dump (clang -Xclang -ast-dump), то clang, по крайней мере, содержит определения этих функций-друзей, но кажется, что невозможно найти их при использовании (ошибка вывода аргумента шаблона?).

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

В частности, я не хочу иметь строку шаблонных функций, которые должны быть реализованы в каждом теге (я только что показал один элемент метаданных, и в рабочем коде их много). некоторые из которых получают дополнительную информацию из комбинаций аргументов шаблона и / или другой информации типа). Разработанное выше оригинальное решение привело к созданию очень чистого, расширяемого и обслуживаемого кода. Оборачивать все это в какой-то сложный макрос было бы абсолютно худшим сценарием!

Здесь есть аналогичный вопрос и ответ здесь , но я не могу понять, как заставить это решение работать в этом сценарии, поскольку аргумент функции friend - это не сам родительский класс, а аргумент шаблона об этом.

Изменение функции GetId на MetadataImpl<...> в качестве аргумента не будет жизнеспособным решением, поскольку использование функций становится совершенно нецелесообразным. Места, из которых вызываются функции, просто хотят предоставить сам тег.

Заранее спасибо за любую помощь!

Ответы [ 3 ]

2 голосов
/ 28 апреля 2019

Причина, по которой он работал раньше, в том, что в gcc есть ошибки. Это не был стандартный C ++ и, скорее всего, никогда не будет. Но это

namespace 
{
    template<typename T>
    struct flag
    {
        friend constexpr unsigned adl(flag<T>);
    };

    template <typename T, unsigned n>
    class meta
    {
        friend constexpr unsigned adl(flag<T>)
        {
            return n;
        }
    };

    template<typename T>
    constexpr auto getId()
    {
        return adl(flag<T>{});
    }
}

И вы можете написать то же самое, что и раньше

template class meta<int, 1>;
template class meta<double, 2>;

auto foo()
{
    return getId<int>();
}

Обратите внимание на анонимное пространство имен, вы запускаете ODR, если у вас его нет.

0 голосов
/ 28 апреля 2019

Так что, возможно, это нарушает ваше требование «без строк шаблонов», но вы можете использовать вспомогательную структуру tag:

template <typename T> struct tag {};
template <> struct tag<int> {
    static constexpr unsigned Id = 1;
    // any more customization points here
};
template <> struct tag<double> {
    static constexpr unsigned Id = 2;
};

(Это также позволит избежать многих явных реализаций).Реализация метаданных будет выглядеть следующим образом:

template <typename Tag>
class MetadataImpl
  {
  friend unsigned GetId(MetadataImpl)
    { return Tag::Id; }
  };

, и теперь вы можете написать помощник для вызова ADL GetId.

template <typename T>
unsigned GetId() {
  return GetId(MetadataImpl<tag<T>>());
}

Демо .

0 голосов
/ 28 апреля 2019

Почему бы вам просто не написать GetId как свободную функцию и специализировать ее по мере необходимости?

template <typename Tag>
unsigned GetId()
{
  return /* default value */;
}

template <> unsigned GetId<int>   () { return 1; }
template <> unsigned GetId<double>() { return 2; }
// ...

Замена регулярного выражения может помочь вам преобразовать явные экземпляры шаблона класса в эти специализации шаблона функции.,(Это одно из немногих обстоятельств, при которых имеет смысл специализировать шаблон функции.)


Если вам не нужно значение по умолчанию, просто определите основную функцию как = delete: (C++ 11)

template <typename Tag>
unsigned GetId() = delete;

Если вы можете использовать шаблоны переменных, (C ++ 14) вы можете сделать код красивее:

template <typename Tag>
unsigned Id = /* default value */;

template <> unsigned Id<int>    = 1;
template <> unsigned Id<double> = 2;
// ...
...