Вопрос по c++ – Определить количество бит в целочисленном типе во время компиляции

7

NOTE: Я добавил похожую, но значительно упрощенную версию проблемы вНеоднозначная перегрузка функций типа `msg (long)` с кандидатами `msg (int32_t)` и `msg (int64_t)`, Эта версия имеет преимущество полного скомпилированного примера в одном файле.

Problem

У меня есть библиотека C с функциями, такими как

obj_from_int32(int32_t& i);
obj_from_int64(int64_t& i);
obj_from_uint32(uint32_t& i);
obj_from_uint64(uint64_t& i);

В этом случае типыint32_t и т. д.not std единицы - они определены реализацией, в данном случае - массивом символов (в следующем примере преобразование я пропустил - это не меняет вопрос о сопоставлении типов интегралов с конкретной функцией на основе количества битов в интегральный тип).

У меня есть второй интерфейс класса C ++, который имеет конструкторы, такие как

MyClass(int z);
MyClass(long z);
MyClass(long long z);
MyClass(unsigned int z);
MyClass(unsigned long z);
MyClass(unsigned long long z);

Обратите внимание, я не могу заменить этот интерфейсstd::int32_t типы стилей - если бы я мог, мне бы не пришлось задавать этот вопрос;)

Проблема в том, как правильно назватьobj_from_ Функция основана на количестве бит в целочисленном типе.

Proposed Solutions

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

Solution 1

ПредоставленоУра и hth. Альф, Комментарии с этого момента являются моими собственными - не стесняйтесь комментировать и / или редактировать.

Advantages  - Довольно просто (по крайней мере, по сравнению сboost::enable_if) - Doesn't rely on 3rd party library (as long as compiler supports tr1)

* Недостатки **  - Если больше функций (например,anotherObj_from_int32 и т. д.), требуется гораздо больше кода

Это решение может быть найдено ниже - посмотрите, это изящно!

Solution 2

Advantages

Once the ConvertFromIntegral functions are done, adding new functions that need the conversion is trivial - simply write a set overloaded on int32_t, int64_t and unsigned equivalents.

Keeps use of templates to one place only, they don't spread as the technique is reused.

Disadvantages

Might be overly complicated, using boost::enable_if. Somewhat mitigated by the fact this appears in once place only.

Поскольку это мое собственное, я не могу принять это, но вы можете выразить это, если считаете, что это аккуратно (и, очевидно, некоторые людиnot я думаю, что это аккуратно, вот почему я думаю об этом, я думаю!) Спасибо всем, кто поделился идеями!

Решение включает в себя функцию преобразования изint, long а такжеlong long вint32_t а такжеint64_t (и аналогично для неподписанных версий). Это в сочетании с другим набором функций, перегруженных наint32_t, int64_t и беззнаковые эквиваленты. Эти две функции могут быть объединены, но первые функции преобразования образуют удобный набор утилит, который можно использовать повторно, а затем второй набор функций тривиально прост.

// Utility conversion functions (reuse wherever needed)
template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(int32_t) && boost::is_signed<InputT>::value,
 int32_t>::type ConvertFromIntegral(InputT z) { return static_cast<int32_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(int64_t) && boost::is_signed<InputT>::value, 
int64_t>::type ConvertFromIntegral(InputT z) { return static_cast<int64_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(uint32_t) && boost::is_unsigned<InputT>::value, 
uint32_t>::type ConvertFromIntegral(InputT z) { return static_cast<uint32_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(uint64_t) && boost::is_unsigned<InputT>::value, 
uint64_t>::type ConvertFromIntegral(InputT z) { return static_cast<uint64_t>(z); }

// Overload set (mock implementation, depends on required return type etc)
void* objFromInt32 (int32_t i)   { obj_from_int32(i); }
void* objFromInt64 (int64_t& i)  { obj_from_int64(i); }
void* objFromUInt32(uint32_t& i) { obj_from_uint32(i); }
void* objFromUInt64(uint64_t& i) { obj_from_uint64(i); }

// Interface Implementation
MyClass(int z) : _val(objFromInt(ConvertFromIntegral(z))) {}
MyClass(long z): _val(objFromInt(ConvertFromIntegral(z))) {}
MyClass(long long z): _val(objFromInt(ConvertFromIntegral(z))) {}
MyClass(unsigned int z): _val(objFromInt(ConvertFromIntegral(z))) {}
MyClass(unsigned long z): _val(objFromInt(ConvertFromIntegral(z))) {}
MyClass(unsigned long long z): _val(objFromInt(ConvertFromIntegral(z))) {}

Упрощенный (один компилируемый.cpp!) версия решения дана наНеоднозначная перегрузка функций типа `msg (long)` с кандидатами `msg (int32_t)` и `msg (int64_t)`

Теперь я понимаю: это не сработает, потому что библиотека C использует массив char в качестве основного типа. Я не вставил это, потому что это запутывает вопрос, но там есть приведение для преобразования между типами. Я подтвердил, что часть работает путем жесткого соединения функций (т.е. предположим, что int 32-битный, и просто подключите его). Zero
Это отличается от примера, который я привел? Я использовал типы наддува для переносимости, но я пробовал и стандартные, тоже результат. (Чтобы уточнить: звучит как хороший ответ для меня, если бы я мог заставить его работать!) Zero
Вероятно, стоит отредактировать, может быть другой способ, чем решение вашей текущей попытки. О, и это просто поразило меня, постарайтесь избежать перегрузки как для подписанного, так и для неподписанного типов в одном наборе перегрузки. Luc Danton
Разница в том, что вы напрямую сопоставляете типы, которые использует API. Ваше текущее решение не обязательно (и не будет при использовании стандартных целочисленных типов). Luc Danton
Вы могли бы написать набор перегрузки, который принимаетint32_t, int64_t и так далее, что переходит к соответствующемуobj_from_* и назвать это от конструкторов. Однако это не гарантирует, что вы позвонитеexact соответствие, только то, что вы получите лучшее соответствие (в соответствии с правилами разрешения перегрузки), поэтому я понятия не имею, стоит ли это ответа. Luc Danton

Ваш Ответ

5   ответов
3

rd партийные функции & # x2026;

void obj_from_int32( int32_bytes_t& i );
void obj_from_int64( int64_bytes_t& i );
void obj_from_uint32( uint32_bytes_t& i );
void obj_from_uint64( uint64_bytes_t& i );

Вы можете позвонить в "правильный" такая функция для встроенного типа выглядит следующим образом:

template< int nBytes, bool isSigned >
struct ThirdParty;

template<>
struct ThirdParty< 4, true >
{
    template< class IntegralT >
    static void func( IntegralT& v )
    { obj_from_int32( v ) }    // Add whatever conversion is required.
};

// Etc., specializations of ThirdParty for unsigned and for 8 bytes.

template< class IntegralT >
void myFunc( IntegralT& v )
{ ThirdParty< sizeof( v ), std::is_signed< IntegralT >::value >::func( v ); }
@ Zero: я не совсем понимаю, что вы имеете в виду - "продублировано"?
@Zero: & quot; потребует & quot; это конечно неправильно. однако я не понимаю ситуацию, на которую вы ссылаетесь, и поэтому пока не могу посоветовать вам более разумный способ разобраться с тем, что есть. Можете ли вы дать больше информации о 15 функциях, что они делают и каковы их фактические типы аргументов? примечание: я немного разочарован погоней заmoving target Вот. поэтому, если возможно, обновите свой вопрос с подробностями в новом разделе.
@ cheers-and-hth-alf Чтобы уточнить, речь идет о количестве кода, необходимого по мере роста числа функций, требующих этого метода. В этом вопросе есть одна функция, в моем коде продукта у меня 15. Специализации шаблонов более сложны, чем однострочныеvoid* makeObjectA(int32_t z) { return make_objectA_int32(z); } в наборе перегрузки. Кроме того, вам не нужны такие функции, как вашmyFunc каждый раз, так как вы можете использовать один и тот жеConvertIntegral на сайте вызова любой функции. Zero
Например три классаdecimal32, decimal64, decimal128 у всех естьctor это принимает единственный, целочисленный аргумент типа. напримерdecimal32(int z): Я могу реализовать это какdecimal32(int z) : _val(decimal32fromIntegral(ConvertIntegral(z))) и четыре однострочных помощника, которые выглядят какDEC32T decimal32fromIntegral(int32_t z) { return dec32_from_int32(z); }, DEC32T - это непрозрачный тип библиотеки. В целом мы отобразили 4 * 3 = 12 функций из C API - хотя на самом деле есть только одна "семантическая". функция: построение DecmialT из IntT. Больше классов или больше семантических функций означает больше кода. Zero
Это аккуратно! Одним недостатком является каждая новая функция (скажем,anotherObj_from_*) требует немного больше кода, так как всеThirdParty специализации должны быть продублированы. Можно ли адаптировать эту технику, чтобы получить результат, аналогичныйConvertIntegral предлагаемого решения в вопросе? Все, что я могу придумать, кажется по меньшей мере таким же сложным, какboost::enable_if, хотя без недостатка для сторонней библиотеки. Zero
0

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

void foo(unsigned int);
void foo(long);

затемfoo(0) является неоднозначным, как преобразования (или, возможно, продвижение) изint как дляunsigned int а такжеlong ранжируются одинаково.

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

Это верно, вы не можете позвонитьfoo(0), но если вы используете квалифицированный числовой литерал, вы сможете избежать двусмысленности.foo(0u); foo(0l);
@Greg Если вы посмотрите на конструкторы, никакие литералы не участвуют.0 был выбран в качестве значения типаint, так0l упускает суть.
1

это можно решить тривиально во время выполнения, используяif(sizeof(int)==sizeof(int32_t)) стиль веток. Чтобы сделать это во время компиляции,boost::enable_if может быть использован.

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(int32_t) && boost::is_signed<InputT>::value,
 int32_t>::type ConvertIntegral(InputT z) { return static_cast<int32_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(int64_t) && boost::is_signed<InputT>::value, 
int64_t>::type ConvertIntegral(InputT z) { return static_cast<int64_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(uint32_t) && boost::is_unsigned<InputT>::value, 
uint32_t>::type ConvertIntegral(InputT z) { return static_cast<uint32_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(uint64_t) && boost::is_unsigned<InputT>::value, 
uint64_t>::type ConvertIntegral(InputT z) { return static_cast<uint64_t>(z); }

Везде, где вам нужно преобразовать целочисленный тип вint32_t, int64_t, uint32_t или жеuint64_t просто позвоните как:

ConvertIntegral(long(5));  // Will return a type compatible with int32_t or int64_t

ConvertIntegral Функция может быть объединена сint32_t а такжеint64_t набор перегрузки для полного решения. В качестве альтернативы, показанная методика может быть встроена в набор перегрузки.

Кроме того, вышеупомянутое может быть дополнительно улучшено путем отключения для нецелых типов. Для полного примера использования функций см.Неоднозначная перегрузка функций типа `msg (long)` с кандидатами `msg (int32_t)` и `msg (int64_t)`

& quot; Чтобы сделать это наcompile time, boost::enable_if... & Quot;
@Managu Конечно (снова) !! Спасибо ;) Zero
Спасибо @Managu, который побудил меня подумать о подписанной проблеме Zero
2

boost::enable_if и вспомогательный шаблон для выбора типа операции, которую вы ищете?

Что-то вроде этого:

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <iostream>

template <typename T, typename Dummy=void> struct helper;

// Handle signed integers of size 1 (8 bits)
template <typename T> struct helper<T, 
    typename boost::enable_if_c<
        boost::is_integral<T>::value && 
        (sizeof(T)==1) &&
        (static_cast<T>(-1) < static_cast<T>(0)) >::type>
{
    static void do_stuff(T const& ) {std::cout<<"signed, size 1"<<std::endl;}
};

// Handle unsigned integers of size 1 (8 bits)
template <typename T> struct helper<T, 
    typename boost::enable_if_c<
        boost::is_integral<T>::value &&
        (sizeof(T)==1) &&
        (static_cast<T>(-1) > static_cast<T>(0)) >::type>
{
    static void do_stuff(T const& ) {std::cout<<"unsigned, size 1"<<std::endl;}
};

// Handle signed integers of size 2 (16 bits)
template <typename T> struct helper<T, 
    typename boost::enable_if_c<
        boost::is_integral<T>::value && 
        (sizeof(T)==2) &&
        (static_cast<T>(-1) < static_cast<T>(0)) >::type>
{
    static void do_stuff(T const& ) {std::cout<<"signed, size 2"<<std::endl;}
};

// And so on and so forth....

// Use a function for type erasure:
template <typename T> void do_stuff(T const& value)
{
    helper<T>::do_stuff(value);
}

int main()
{
    do_stuff(static_cast<unsigned char>(0)); // "unsigned, size 1"
    do_stuff(static_cast<signed short>(0));  // "signed, size 2"
}

Более полный список (и доказательство того, что он работает с GCC по крайней мере) вhttp://ideone.com/pIhdq.

Изменить: Или более просто, но, возможно, с меньшим охватом: (используя стандартные целочисленные типы)

template <typename T> struct helper2;
template <> struct helper2<uint8_t> {static void do_stuff2(uint8_t ) {...}};
template <> struct helper2<int8_t> {static void do_stuff2(int8_t ) {...}};
template <> struct helper2<uint16_t> {static void do_stuff2(uint16_t ) {...}};
template <> struct helper2<int16_t> {static void do_stuff2(int16_t ) {...}};
// etc.
template <typename T> void do_stuff2(T value) {helper2<T>::do_stuff2(value);}
Принятие, потому что это лучший ответ, и спасибо, что подсказали мне подумать о подписанной / неподписанной проблеме - я думаю, что это можно упростить (см. Редактирование в вопросе), но приветствую любые отзывы! Zero
Я говорю «возможно с меньшим охватом» потому что, например, на моей платформе,char не является ниuint8_t==unsigned char ниint8_t==signed char, Более того, сопоставление с шаблоном по типу даст вам необходимую степень дискриминации, в отличие от перегрузки. Но вы должны убедиться, что вы перечислили все типы, которые вам небезразличны. Или скрыть их неявно, как я сделал в первом примере сenable_if магия.
Если вы не заботитесь о подписи, вы, конечно, можетеstatic_cast<T>(-1) < static_cast<T>(0) пункты, и иметь дело только с половиной специализацийstruct helper.
2

длинная причина здесь двусмысленность. Линия

MyClass(long z): _val(objFromInt(z)) {}

должно быть изменено на что-то вроде:

MyClass(long z): _val(sizeof(long) == 4 ? static_cast<int32_t>(z) : static_cast<int64_t>(z)))) {}

Обратите внимание, что вы, вероятно, столкнетесь с подобной проблемой с long long на 64-битном gcc.

@Zero Единственное, что приходит на ум - это constexpr и шаблоны;?: Решение более элегантное.
Это, конечно, сработает, но я продержусь немного дольшеcompile time решение. Информация все там во время компиляции ... Zero
sizeof is время компиляции, поэтому выражение сводится кconstant-expression ? something : something-elseи средство удаления мертвого кода компилятора оптимизирует это.
@moshbear Меня беспокоит только количество дублируемого кода, так как будет добавлено больше классов (в настоящее время у меня есть 16 функций, которые требуют эту технику ...). По этой причине я все еще склоняюсь кboost::enable_if Решение, сложный бит нужно сделать только один раз. Например, еслиConvertIntegral были предоставлены какboost Функция библиотеки Я бы, конечно, подумал, что это самое элегантное решение. Zero
@moshbear Интересный момент ... Я определенно согласен с тем, что полагаться на такие простые оптимизации компилятора всегда хорошая идея на практике. Однако можно ли его превратить в вспомогательную функцию (поскольку вы не знаете тип возвращаемого значения?) Если нет, я думаю, что это существенный недостаток, поскольку очень трудно масштабировать решение для многих функций. Zero

Похожие вопросы