касты из одного енума в другой
От: Brice Tribbiani Россия http://vzaguskin.github.io
Дата: 29.05.17 13:38
Оценка:
Привет,
Есть куча енумов в интерфейсе используемой библиотеки.
Интерфейс следующего слоя абстракции используют примерно эти же енумы, но под другими именами и слегка другими полями.

Требуются методы преобразования енумов каждого типа.

Ну, то есть:
typedef enum
{
    STATUS_OK,
    STATUS_WARNING,
    STATUS_ERROR,
    STATUS_ERROR2,
    STATUS_ERROR3,
}eLIBRARY_STATUS;

typedef enum
{
    PROGRAM_STATUS_OK,
    PROGRAM_STATUS_WARNING,
    PROGRAM_STATUS_ERROR,

}ePROGRAM_STATUS;

ePROGRAM_STATUS LibraryStatus2ProgramStatus(eLIBRARY_STATUS ls)
{
    switch (ls)
    {
        case STATUS_OK:
            return     PROGRAM_STATUS_OK;
        case STATUS_WARNING:
            return     PROGRAM_STATUS_WARNING;
        default:
            return PROGRAM_STATUS_ERROR;
    }
}


Так вот, не хочется создавать кучу функций вида LibraryStatus2ProgramStatus. Вместо этого хотелось бы чего-нибудь, типа перегрузки оператора присвоения или каста как функции — нечлена, аля
//жалко так нельзя
ePROGRAM_STATUS operator=(eLIBRARY_STATUS& from)
{
    //тут тело функции LibraryStatus2ProgramStatus
}

//и потом в коде:
eLIBRARY_STATUS lst;
ePROGRAM_STATUS pst;
pst = lst;

//ну или хотябы:
ePROGRAM_STATUS operator ePROGRAM_STATUS(eLIBRARY_STATUS)
{
    //тут тело функции LibraryStatus2ProgramStatus
}

//и потом в коде:
eLIBRARY_STATUS lst;
ePROGRAM_STATUS pst;
pst = (ePROGRAM_STATUS)lst; //чтобы тут не тупой каст инта в инт, а правильный.


А как это можно написать не по уродски?
хотел уже на боковую
папаху снял и сапоги
но в комментариях проснулись
враги
Re: касты из одного енума в другой
От: Erop Россия  
Дата: 29.05.17 15:29
Оценка: 2 (1)
Здравствуйте, Brice Tribbiani, Вы писали:

BT>А как это можно написать не по уродски?

Тебя интересует сам по себе вызов перекодирующей функции?

1) Как много таких пар перечислений?
2) Как сложно писать сами перекодирующие функции?

Например можно так:
template<typename TTo, typename TFrom>
TTo enum_cast( TFrom );

template<> ePROGRAM_STATUS  enum_cast<ePROGRAM_STATUS, eLIBRARY_STATUS>( eLIBRARY_STATUS src )
{
    // тут код функции LibraryStatus2ProgramStatus
}


Если не хочется в точках вызова писать везде ePROGRAM_STATUS, то можно ещё такой шаблон добавить:
template<typename TTo, typename TFrom)
TTo SetEnumTo( TTo& dst, TFrom src ) { return dst = enum_cast<TTo>( src ); }

// пример:

ePROGRAM_STATUS res = SetEnumTo( res, STATUS_WARNING );
SetEnumTo( res, STATUS_OK );
// И т. д...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: касты из одного енума в другой
От: Brice Tribbiani Россия http://vzaguskin.github.io
Дата: 29.05.17 16:20
Оценка:
Здравствуйте, Erop, Вы писали:

E>Тебя интересует сам по себе вызов перекодирующей функции?

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

E>1) Как много таких пар перечислений?

Туева хуча. Но большинство уже перекодируются прямо в коде, это я так, на будущее.

E>2) Как сложно писать сами перекодирующие функции?

Не понял вопроса. Ну, я же привел пример функции, как сложно её написать?


E>Например можно так:



Ага, спасибо. Я примерно это же и нарисовал.
template<typename T, typename U>
T my_enum_cast(U in )
{
    //Will fire compiler error when this generic template is instantiated
    BOOST_STATIC_ASSERT_MSG(!std::is_integral<U>::value, "No conversion defined for this enum types, please define one.");
    return (T)in;
}

template<>
ePROGRAM_STATUS my_enum_cast(eLIBRARY_STATUS err)
{
    return LibraryStatus2ProgramStatus(err);
}

//вызов
eLIBRARY_STATUS err;
eServiceErrorCode = my_enum_cast<ePROGRAM_STATUS>(err);


Тут, правда, пара моментов мучает:
1. А мой статик ассерт насколько портабелен? Если вместо этого извращения с !std::is_integral<U>::value подставить просто false, он всегда срабатывает, даже когда generic специализация не инстанциируется.
2. А может, разрешить таки c-style каст по умолчанию?
3. Как я понимаю, то, что я могу в точке вызова не писать <ePROGRAM_STATUS, eLIBRARY_STATUS >, а достаточно <ePROGRAM_STATUS> — это только с с++11, или в 03 тоже прокатит? И что мешает компилятору вывести и второй тип тоже, который возвращается? Чтобы сразу писать:

eLIBRARY_STATUS err;
ePROGRAM_STATUS eServiceErrorCode = my_enum_cast(err);



E>Если не хочется в точках вызова писать везде ePROGRAM_STATUS, то можно ещё такой шаблон добавить:

E>
template<typename TTo, typename TFrom)
E>TTo SetEnumTo( TTo& dst, TFrom src ) { return dst = enum_cast<TTo>( src ); }

E>// пример:

E>ePROGRAM_STATUS res = SetEnumTo( res, STATUS_WARNING );
E>SetEnumTo( res, STATUS_OK );
E>// И т. д...

E>


А это, как я понимаю, и есть воркэроунд для последнего вопроса.
Не знаю, как-то не человеко-читаемо, по моему.
хотел уже на боковую
папаху снял и сапоги
но в комментариях проснулись
враги
Re[3]: касты из одного енума в другой
От: Erop Россия  
Дата: 29.05.17 16:46
Оценка: 4 (1)
Здравствуйте, Brice Tribbiani, Вы писали:

BT>
BT>template<typename T, typename U>
BT>T my_enum_cast(U in )
BT>{
BT>    //Will fire compiler error when this generic template is instantiated
BT>    BOOST_STATIC_ASSERT_MSG(!std::is_integral<U>::value, "No conversion defined for this enum types, please define one.");
BT>    return (T)in;
BT>}

BT>template<>
BT>ePROGRAM_STATUS my_enum_cast<ePROGRAM_STATUS, eLIBRARY_STATUS>(eLIBRARY_STATUS err)
BT>{
BT>    return LibraryStatus2ProgramStatus(err);
BT>}

BT>//вызов
BT>eLIBRARY_STATUS err;
BT>eServiceErrorCode = my_enum_cast<ePROGRAM_STATUS>(err);

BT>


BT>Тут, правда, пара моментов мучает:

BT>1. А мой статик ассерт насколько портабелен? Если вместо этого извращения с !std::is_integral<U>::value подставить просто false, он всегда срабатывает, даже когда generic специализация не инстанциируется.
Можно шаблонную функцию вообще только объявить, но не определять...

BT>2. А может, разрешить таки c-style каст по умолчанию?

Зачем?

BT>3. Как я понимаю, то, что я могу в точке вызова не писать <ePROGRAM_STATUS, eLIBRARY_STATUS >, а достаточно <ePROGRAM_STATUS> — это только с с++11, или в 03 тоже прокатит? И что мешает компилятору вывести и второй тип тоже, который возвращается? Чтобы сразу писать:

Прокатит, а мешают правила вывода.


E>>Если не хочется в точках вызова писать везде ePROGRAM_STATUS, то можно ещё такой шаблон добавить:

E>>
template<typename TTo, typename TFrom)
E>>TTo SetEnumTo( TTo& dst, TFrom src ) { return dst = enum_cast<TTo>( src ); }

E>>// пример:

E>>ePROGRAM_STATUS res = SetEnumTo( res, STATUS_WARNING );
E>>SetEnumTo( res, STATUS_OK );
E>>// И т. д...

E>>


BT>А это, как я понимаю, и есть воркэроунд для последнего вопроса.

BT>Не знаю, как-то не человеко-читаемо, по моему.

А так:
Let( res ) = STATUS_OK;


или так:
res =let= STATUS_OK;


  Как это устроить
template<typename TFrom>
struct enum_proxy {
                TFrom Data;
 
                template<typename TTo> operator TTo() const { return enum_cast<TTo>(Data); }
};
 
template<>
struct enum_proxy<void> {
                template<typename TFrom>
                enum_proxy<TFrom> operator = (TFrom from) const { return enum_proxy<TFrom>{ from }; }
};
 
const enum_proxy<void> let;
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Отредактировано 29.05.2017 19:23 Erop . Предыдущая версия . Еще …
Отредактировано 29.05.2017 17:07 Erop . Предыдущая версия .
Re[3]: касты из одного енума в другой
От: Erop Россия  
Дата: 29.05.17 17:04
Оценка:
Здравствуйте, Brice Tribbiani, Вы писали:

BT>Ага. Не, ну если ты знаешь еще изящный способ перекодирования, без написания свитчей руками, я тоже не откажусь

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

E>>1) Как много таких пар перечислений?

BT>Туева хуча. Но большинство уже перекодируются прямо в коде, это я так, на будущее.

Ну, тут может быть лучше другая стратегия. Типа пишешь набор функций, перекодирующих между слоями.
Ну там семейство toProgramEnum для перевода из перечислений разных библ в перечисления программы, например.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: касты из одного енума в другой
От: Brice Tribbiani Россия http://vzaguskin.github.io
Дата: 30.05.17 09:29
Оценка:
Здравствуйте, Erop, Вы писали:


E>Можно шаблонную функцию вообще только объявить, но не определять...


Ошибка переползает на время линковки, это не совсем то, что хочется. Со статик ассертом сразу понятно, что к чему. Переписал его пока вот в таком виде:
BOOST_STATIC_ASSERT_MSG(!(std::is_enum<U>::value || !std::is_enum<U>::value) , "No conversion defined for this enum types, please define one.");


Но не знаю, может ли какой шибко умный компайлер соптимизировать условие сразу в false и ругнуться без инстанциации.

BT>>2. А может, разрешить таки c-style каст по умолчанию?

E>Зачем?

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

BT>>Не знаю, как-то не человеко-читаемо, по моему.


E>А так:

E>
Let( res ) = STATUS_OK;


Ну, вот так, наверно, можно, если вместо let какое-нибудь говорящее имя придумать. Хотя пока что enum_cast<ePROGRAM_STATUS> кажется оптимальным балансом количества писанины/легкости понимания.
хотел уже на боковую
папаху снял и сапоги
но в комментариях проснулись
враги
Re[3]: касты из одного енума в другой
От: N. I.  
Дата: 31.05.17 09:07
Оценка: 2 (1)
Brice Tribbiani:

E>>2) Как сложно писать сами перекодирующие функции?

BT>Не понял вопроса. Ну, я же привел пример функции, как сложно её написать?

Конкретно в данном случае можно было бы использовать static_cast

#include <iostream>

typedef enum
{
    STATUS_OK,
    STATUS_WARNING,
    STATUS_ERROR,
    STATUS_ERROR2,
    STATUS_ERROR3,
}eLIBRARY_STATUS;

typedef enum
{
    PROGRAM_STATUS_OK,
    PROGRAM_STATUS_WARNING,
    PROGRAM_STATUS_ERROR,

}ePROGRAM_STATUS;

enum OtherEnum
{
    VALUE_0,
    VALUE_1
};

template <class To>
    To enum_cast(eLIBRARY_STATUS x) noexcept = delete;
template <>
    inline ePROGRAM_STATUS enum_cast<ePROGRAM_STATUS>(eLIBRARY_STATUS x) noexcept
{
    if (x > STATUS_ERROR)
        return PROGRAM_STATUS_ERROR;
    return static_cast<ePROGRAM_STATUS>(x);
}

#define PRINT_VALUE(...) (std::cout << #__VA_ARGS__ " : " << (__VA_ARGS__) << std::endl)

int main()
{
    PRINT_VALUE(enum_cast<ePROGRAM_STATUS>(STATUS_WARNING) == PROGRAM_STATUS_ERROR);
    PRINT_VALUE(enum_cast<ePROGRAM_STATUS>(STATUS_ERROR) == PROGRAM_STATUS_ERROR);
    PRINT_VALUE(enum_cast<ePROGRAM_STATUS>(STATUS_ERROR3) == PROGRAM_STATUS_ERROR);
    /// enum_cast<OtherEnum>(STATUS_ERROR); // would be ill-formed
}

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

BT>Тут, правда, пара моментов мучает:

BT>1. А мой статик ассерт насколько портабелен? Если вместо этого извращения с !std::is_integral<U>::value подставить просто false, он всегда срабатывает, даже когда generic специализация не инстанциируется.

Если из шаблона нельзя получить ни одну валидную специализацию, то программа ill-formed (в данном случае компилятор может, но не обязан это диагностировать).
Re[5]: касты из одного енума в другой
От: N. I.  
Дата: 31.05.17 09:10
Оценка: 4 (1)
Brice Tribbiani:

BT>Ошибка переползает на время линковки, это не совсем то, что хочется. Со статик ассертом сразу понятно, что к чему. Переписал его пока вот в таком виде:

BT>
BT>BOOST_STATIC_ASSERT_MSG(!(std::is_enum<U>::value || !std::is_enum<U>::value) , "No conversion defined for this enum types, please define one.");
BT>


BT>Но не знаю, может ли какой шибко умный компайлер соптимизировать условие сразу в false и ругнуться без инстанциации.


Теоретически компилятор может вывести, что условие !(std::is_enum<U>::value || !std::is_enum<U>::value) никогда не выполняется и по данному шаблону ничего нельзя сгенерировать. Для надёжности можно было бы использовать какой-нибудь вспомогательный шаблон, который позволял бы сопоставлять произвольному типу некое фиксированное значение, но при этом компилятор не мог бы считать его фиксированным из-за потенциальной возможности добавлять частичные или явные специализации, где значение могло бы быть другим:

// DO NOT SPECIALIZE!
template <class Dependency, class ValueType, ValueType Value>
    struct dependent_value
        { static ValueType const value = Value; };
template <class Dependency, class ValueType, ValueType Value>
    ValueType const dependent_value<Dependency, ValueType, Value>::value;

BOOST_STATIC_ASSERT_MSG(dependent_value<U, bool, false>::value , "No conversion is defined for these enum types.");
Отредактировано 31.05.2017 9:14 N. I. . Предыдущая версия .
Re[4]: касты из одного енума в другой
От: Brice Tribbiani Россия http://vzaguskin.github.io
Дата: 31.05.17 10:00
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Brice Tribbiani:


E>>>2) Как сложно писать сами перекодирующие функции?

BT>>Не понял вопроса. Ну, я же привел пример функции, как сложно её написать?

NI>Конкретно в данном случае можно было бы использовать static_cast



NI>но это несколько менее надёжно, чем вариант со switch, т.к. тут используется соответствие численных значений, а при некоторых изменениях определения какого-либо из этих перечислений такое соответствие может быть нарушено.


Ну, это понятно.

BT>>Тут, правда, пара моментов мучает:

BT>>1. А мой статик ассерт насколько портабелен? Если вместо этого извращения с !std::is_integral<U>::value подставить просто false, он всегда срабатывает, даже когда generic специализация не инстанциируется.

NI>Если из шаблона нельзя получить ни одну валидную специализацию, то программа ill-formed (в данном случае компилятор может, но не обязан это диагностировать).


То есть, как я понял:

=delete, как я понимаю, с c++11. Но, если есть возможность использовать его — то надо его и использовать. Или к нему тоже применимо вот это "Если из шаблона нельзя получить ни одну валидную специализацию, то программа ill-formed"?

static_assert(false,"") — ill-formed, потому что "из шаблона нельзя получить ни одну валидную специализацию", и поэтому компилятор выдает его диагностику, несмотря на отсутствие инстанциации.

static_assert<dependent_value<U, bool, false>::value, ""> — тоже будет портабельно.

Правильно?
хотел уже на боковую
папаху снял и сапоги
но в комментариях проснулись
враги
Re[5]: касты из одного енума в другой
От: N. I.  
Дата: 01.06.17 09:32
Оценка: 4 (1)
Brice Tribbiani:

BT>То есть, как я понял:


BT>=delete, как я понимаю, с c++11.


Верно.

BT>Но, если есть возможность использовать его — то надо его и использовать.


Ну, скажем так, это использование =delete по его прямому назначению.

BT>Или к нему тоже применимо вот это "Если из шаблона нельзя получить ни одну валидную специализацию, то программа ill-formed"?


Определение функции как deleted не делает её невалидной, это делает невалидным только использование её некоторыми способами:

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.


BT>static_assert(false,"") — ill-formed, потому что "из шаблона нельзя получить ни одну валидную специализацию", и поэтому компилятор выдает его диагностику, несмотря на отсутствие инстанциации.


Верно. Правило такое:

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.


Под valid неявно подразумевается well-formed. Использование static_assert(false,"") делает программу ill-formed:

If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and the resulting diagnostic message (1.4) shall include the text of the string-literal, except that characters not in the basic source character set (2.3) are not required to appear in the diagnostic message.


BT>static_assert<dependent_value<U, bool, false>::value, ""> — тоже будет портабельно.


BT>Правильно?


Если поправить скобочки

static_assert(dependent_value<U, bool, false>::value, "");

то да. В этом случае потенциально возможно существование U, для которого dependent_value<U, bool, false>::value равно true.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.