Информация об изменениях

Сообщение Re: ADL не ADL? Или как? от 07.01.2024 20:42

Изменено 07.01.2024 21:01 rg45

Re: ADL не ADL? Или как?
Здравствуйте, Marty, Вы писали:

M>Есть у меня сериализация/десериализация enum'ов.


M>Она на внешнем генераторе, в результате его работы в том же пространстве имён, в котором объявлен enum, появляется функция:

M>
M>inline
M>EnumType enum_deserialize(const std::string &str, EnumType defVal);
M>


M>Обычно у меня есть EnumType::invalidValue, я его передаю как defVal, и если на выходе получил EnumType::invalidValue, то кидаю исключение.

M>Поиск реализации идёт через ADL по дефолтному значению, и всё в общем-то работает.

M>Есть ещё функция

M>
M>inline
M>EnumType enum_deserialize_##EnumType(const std::string &str);
M>


M>Она сама умеет кидать исключение, но её имя генерится на макросах и зависит от имени enum'а.


M>Теперь я хочу сделать шаблонную функцию enum_deserialize, чтобы тип enum'а она получала как параметр шаблона.

M>Пока я сделал, основываясь на том, что EnumType::invalidValue у меня всегда -1, и я использую первую версию, передаю туда инвалида (-1), и на выходе с этим инвалидом сравниваю, и кидаю исключение, если что-то пошло не так.

M>Но это завязано на мои соглашения, что EnumType::invalidValue у меня всегда -1. С enum'ами, константы для которых могут зависеть от внешних спек, такое может не прокатить.


M>Наивная идея была сделать общий шаблон enum_deserialize_throwable, и генерить специализации для него. Но потом я вспомнил, что все сгенерённые функции попадают в то же пространство имён, что и сам enum, и это могут быть любые NS, и точно не то NS, в котором объявлен шаблон, который я хотел специализировать.


M>Как быть?


Честно признаюсь, не уверен, что полностью правильно понял все сценарии, но попробую предложить решение, а вдруг попаду. Ты можешь для каждого энума определить функцию получения дефолтного значения. Разумеется, эта функция должна быть определена в том же пространстве имен, что и сам энум — чтобы она могла быть найденной через ADL. Один лишь нюанс: для того, чтобы ADL правильно работал, у этой функции должен быть один формальный параметр, имеющий тип этого энума. При вызове этих функций можно будет передавать фактический параметр, сконструированный по дефолту. Еще один замечательный момент: эти функции, получающие дефолтный параметр могут быть как шаблонными, так и обычными, нешаблонными. Схематично так:

namespace ns1
{

enum E1
{
   invalidValue = -1,
   . . .
};

enum E2
{
   invalidValue = -1,
   . . .
};

enum E3
{
   None = -42,
   . . .
};

enum E4
{
   // No invalid value defined
   Good1 = 1,
   Good2 = 2,
   Good3 = 3
};

// Comon accessor
template <typename T>
constexpr T enum_default_value(T) { return T::invalidValue; }

// Special accessor for E3
constexpr E3 enum_default_value(E3) { return E3::None; }

// Special accessor for E4
constexpr E4 enum_default_value(E4) { return {}; } // Returns zero-initialized E4 object

} // namespace ns1

Ну и потом где-то дальше использование в произвольном пространстве имен:

namespace ns2
{

void usage_example()
{
  enum_default_value(ns1::E1{}); // returns E1::invalidValue
  enum_default_value(ns1::E2{}); // returns E2::invalidValue
  enum_default_value(ns1::E3{}); // returns E3::None
  enum_default_value(ns1::E4{}); // returns E4{}
}

} // namespace ns2



Можно еще заморочиться и создать универсальную шаблонную обертку вообще для всех-всех типов энумов, которая будет создавать дефолтный экземпляр энума нужного типа и передавать его в "рабочу лошадку" — акксесор:

namespace ns2
{

template <typename T>
constexpr enum_default_value() { return enum_default_value(T{}); }

void usage_example()
{
  enum_default_value<ns1::E1>(); // returns E1::invalidValue
  enum_default_value<ns1::E2>(); // returns E2::invalidValue
  enum_default_value<ns1::E3>(); // returns E3::None
  enum_default_value<ns1::E4>(); // returns E4{}
}
} // namespace ns2

Но это уже для совсем уж больших любителей синтаксического сахара, без этого вполне можно жить, я считаю.
Re: ADL не ADL? Или как?
Здравствуйте, Marty, Вы писали:

M>Есть у меня сериализация/десериализация enum'ов.


M>Она на внешнем генераторе, в результате его работы в том же пространстве имён, в котором объявлен enum, появляется функция:

M>
M>inline
M>EnumType enum_deserialize(const std::string &str, EnumType defVal);
M>


M>Обычно у меня есть EnumType::invalidValue, я его передаю как defVal, и если на выходе получил EnumType::invalidValue, то кидаю исключение.

M>Поиск реализации идёт через ADL по дефолтному значению, и всё в общем-то работает.

M>Есть ещё функция

M>
M>inline
M>EnumType enum_deserialize_##EnumType(const std::string &str);
M>


M>Она сама умеет кидать исключение, но её имя генерится на макросах и зависит от имени enum'а.


M>Теперь я хочу сделать шаблонную функцию enum_deserialize, чтобы тип enum'а она получала как параметр шаблона.

M>Пока я сделал, основываясь на том, что EnumType::invalidValue у меня всегда -1, и я использую первую версию, передаю туда инвалида (-1), и на выходе с этим инвалидом сравниваю, и кидаю исключение, если что-то пошло не так.

M>Но это завязано на мои соглашения, что EnumType::invalidValue у меня всегда -1. С enum'ами, константы для которых могут зависеть от внешних спек, такое может не прокатить.


M>Наивная идея была сделать общий шаблон enum_deserialize_throwable, и генерить специализации для него. Но потом я вспомнил, что все сгенерённые функции попадают в то же пространство имён, что и сам enum, и это могут быть любые NS, и точно не то NS, в котором объявлен шаблон, который я хотел специализировать.


M>Как быть?


Честно признаюсь, не уверен, что полностью правильно понял все сценарии, но попробую предложить решение, а вдруг попаду. Ты можешь для каждого энума определить функцию получения дефолтного значения. Разумеется, эта функция должна быть определена в том же пространстве имен, что и сам энум — чтобы она могла быть найденной через ADL. Один лишь нюанс: для того, чтобы ADL правильно работал, у этой функции должен быть один формальный параметр, имеющий тип этого энума. При вызове этих функций можно будет передавать фактический параметр, сконструированный по дефолту. Еще один замечательный момент: эти функции, получающие дефолтный параметр могут быть как шаблонными, так и обычными, нешаблонными. Схематично так:

namespace ns1
{

enum E1
{
   invalidValue = -1,
   . . .
};

enum E2
{
   invalidValue = -1,
   . . .
};

enum E3
{
   None = -42,
   . . .
};

enum E4
{
   // No invalid value defined
   Good1 = 1,
   Good2 = 2,
   Good3 = 3
};

// Comon accessor
template <typename T>
constexpr T enum_default_value(T) { return T::invalidValue; }

// Special accessor for E3
constexpr E3 enum_default_value(E3) { return E3::None; }

// Special accessor for E4
constexpr E4 enum_default_value(E4) { return {}; } // Returns zero-initialized E4 object

} // namespace ns1

Ну и потом где-то дальше использование в произвольном пространстве имен:

namespace ns2
{

void usage_example()
{
  enum_default_value(ns1::E1{}); // returns E1::invalidValue
  enum_default_value(ns1::E2{}); // returns E2::invalidValue
  enum_default_value(ns1::E3{}); // returns E3::None
  enum_default_value(ns1::E4{}); // returns E4{}
}

} // namespace ns2



Можно еще заморочиться и создать универсальную шаблонную обертку вообще для всех-всех типов энумов, которая будет создавать дефолтный экземпляр энума нужного типа и передавать его в "рабочу лошадку" — акксесор:

namespace ns2
{

template <typename T>
constexpr enum_default_value() { return enum_default_value(T{}); }

void usage_example()
{
  enum_default_value<ns1::E1>(); // returns E1::invalidValue
  enum_default_value<ns1::E2>(); // returns E2::invalidValue
  enum_default_value<ns1::E3>(); // returns E3::None
  enum_default_value<ns1::E4>(); // returns E4{}
}
} // namespace ns2

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