Переопределил operator<< для своего потока, для всех std::intN_t/uintN_t
При выводе unsigned long говорит, что оператор амбигус: "error C2593: 'operator <<' is ambiguous", и перечисляет все int* перегрузки, и ни одна ему не нравится больше других
Глянул, как они определены в MSVC
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
Как видно, unsigned long тут нет. Можно было бы добавить перегрузку для unsigned long, но в другом компиляторе может быть всё по другому, и unsigned long уже будет в этом списке. Как тут быть?
Можно шаблон конечно сделать для целых типов, но пока хотелось бы решить без этого
Здравствуйте, Marty, Вы писали:
M>>>Можно шаблон конечно сделать для целых типов, но пока хотелось бы решить без этого _>>namespace ? M>Что неймспейс? https://godbolt.org/z/WGYabPK6r
то что ваш стрим не обязан быть в std
M>typedef — это просто алиас к существующему типу, перегрузки компилятор видит по оригинальному типа, пофик на алиас.
и что?
Здравствуйте, Marty, Вы писали:
M>Не обязан, и не в std. И что? Я не очень понимаю мысль
Тогда уточните что вам надо
M>И то, что если сделать перегрузку для std::uint64_t, компилятор будет видеть её как перегрузку для unsigned long long
Так потому как это один и тот же тип.
Если надо сделать его как-то иначе выводить сделайте обёртку и явно указывайте что вам надо.
std::uint64_t v1; unsigned long long v2;
stream << OutputWrapper1(v1) << OutputWrapper2(v2);
Здравствуйте, kov_serg, Вы писали:
M>>Не обязан, и не в std. И что? Я не очень понимаю мысль _>Тогда уточните что вам надо
Да вроде в стартовом посте написано вполне доходчиво
Я полагал, что перегрузок по std:(u)intXX_t хватит, чтобы покрыть все варианты интов, но оказалось, что нет. И на другом компиляторе тайпдефы могут быть другими, и если я под MSVC допилю недостающие, то с другим компилятором будет по-другому.
M>>И то, что если сделать перегрузку для std::uint64_t, компилятор будет видеть её как перегрузку для unsigned long long _>Так потому как это один и тот же тип.
Так я вроде про это и говорил, а ты про какие-то namespace
_>Если надо сделать его как-то иначе выводить сделайте обёртку и явно указывайте что вам надо. _>
_>std::uint64_t v1; unsigned long long v2;
_>stream << OutputWrapper1(v1) << OutputWrapper2(v2);
_>
Здравствуйте, Marty, Вы писали:
M>Да вроде в стартовом посте написано вполне доходчиво
нет
M>Я полагал, что перегрузок по std:(u)intXX_t хватит, чтобы покрыть все варианты интов, но оказалось, что нет. И на другом компиляторе тайпдефы могут быть другими, и если я под MSVC допилю недостающие, то с другим компилятором будет по-другому.
А нафига вы их все перегружаете?
Здравствуйте, kov_serg, Вы писали:
_>нафига вы их все перегружаете?
Ну вот мне тоже регулярно требуется разная реализация операций для 16-, 32- и 64-разрядных значений, ибо их вычислительная сложность заметно отличается. Пока операции используются непосредственно, и значения передаются типизированными переменными/константами, все хорошо. Как требуется выполнять операции над литералами, и/или обернуть их в шаблоны — начинается кошмар из-за integral promotions, запретить которые нет никакой возможности. И приходится костылить, на результат невозможно смотреть без отвращения.
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Переопределил operator<< для своего потока, для всех std::intN_t/uintN_t
M>При выводе unsigned long говорит, что оператор амбигус: "error C2593: 'operator <<' is ambiguous", и перечисляет все int* перегрузки, и ни одна ему не нравится больше других
M>Глянул, как они определены в MSVC M>
M>typedef signed char int8_t;
M>typedef short int16_t;
M>typedef int int32_t;
M>typedef long long int64_t;
M>typedef unsigned char uint8_t;
M>typedef unsigned short uint16_t;
M>typedef unsigned int uint32_t;
M>typedef unsigned long long uint64_t;
M>
M>Как видно, unsigned long тут нет. Можно было бы добавить перегрузку для unsigned long, но в другом компиляторе может быть всё по другому, и unsigned long уже будет в этом списке. Как тут быть?
M>Можно шаблон конечно сделать для целых типов, но пока хотелось бы решить без этого
struct S {
S& operator<<(char ) { std::cout<<"char "<<std::endl; return *this; }
S& operator<<(signed char ) { std::cout<<"signed char "<<std::endl; return *this; }
S& operator<<(unsigned char ) { std::cout<<"unsigned char "<<std::endl; return *this; }
S& operator<<(signed short ) { std::cout<<"signed short "<<std::endl; return *this; }
S& operator<<(signed int ) { std::cout<<"signed int "<<std::endl; return *this; }
S& operator<<(signed long ) { std::cout<<"signed long "<<std::endl; return *this; }
S& operator<<(signed long long ) { std::cout<<"signed long long "<<std::endl; return *this; }
S& operator<<(unsigned short ) { std::cout<<"unsigned short "<<std::endl; return *this; }
S& operator<<(unsigned int ) { std::cout<<"unsigned int "<<std::endl; return *this; }
S& operator<<(unsigned long ) { std::cout<<"unsigned long "<<std::endl; return *this; }
S& operator<<(unsigned long long) { std::cout<<"unsigned long long"<<std::endl; return *this; }
Суть: типы short и int (char и short, int и long, long и long long) могут иметь одинаковый размер, оставаясь разными типами. Тяжкое наследие Си.
Типы с фиксированным размером (std::intXX_t), ссылаются на один из этих типов.
Можно представить, что компилятор будет поддерживать какой-то специальный __int64_t, и std::int64_t ссылается на него, но я такого не встречал.
C>struct S {
C> S& operator<<(char ) { std::cout<<"char "<<std::endl; return *this; }
C> S& operator<<(signed char ) { std::cout<<"signed char "<<std::endl; return *this; }
C> S& operator<<(unsigned char ) { std::cout<<"unsigned char "<<std::endl; return *this; }
C> S& operator<<(signed short ) { std::cout<<"signed short "<<std::endl; return *this; }
C> S& operator<<(signed int ) { std::cout<<"signed int "<<std::endl; return *this; }
C> S& operator<<(signed long ) { std::cout<<"signed long "<<std::endl; return *this; }
C> S& operator<<(signed long long ) { std::cout<<"signed long long "<<std::endl; return *this; }
C> S& operator<<(unsigned short ) { std::cout<<"unsigned short "<<std::endl; return *this; }
C> S& operator<<(unsigned int ) { std::cout<<"unsigned int "<<std::endl; return *this; }
C> S& operator<<(unsigned long ) { std::cout<<"unsigned long "<<std::endl; return *this; }
C> S& operator<<(unsigned long long) { std::cout<<"unsigned long long"<<std::endl; return *this; }
C>
C>Суть: типы short и int (char и short, int и long, long и long long) могут иметь одинаковый размер, оставаясь разными типами. Тяжкое наследие Си. C>Типы с фиксированным размером (std::intXX_t), ссылаются на один из этих типов. C>Можно представить, что компилятор будет поддерживать какой-то специальный __int64_t, и std::int64_t ссылается на него, но я такого не встречал.
Здравствуйте, Chorkov, Вы писали:
C>Суть: типы short и int (char и short, int и long, long и long long) могут иметь одинаковый размер, оставаясь разными типами. Тяжкое наследие Си.
Можно еще вот так извернуться если не нравиться разные но одинаковые типы https://godbolt.org/z/4ocTWszE5
C>Типы с фиксированным размером (std::intXX_t), ссылаются на один из этих типов. C>Можно представить, что компилятор будет поддерживать какой-то специальный __int64_t, и std::int64_t ссылается на него, но я такого не встречал.
вот что харектерно, c++ всё есть для того чтобы иметь bigint но std::bigint нет.
Зато еще есть куча всяких целых спец типов __m128i __m256i __m512i
C>>struct S {
C>> S& operator<<(char ) { std::cout<<"char "<<std::endl; return *this; }
C>> S& operator<<(signed char ) { std::cout<<"signed char "<<std::endl; return *this; }
C>> S& operator<<(unsigned char ) { std::cout<<"unsigned char "<<std::endl; return *this; }
C>> S& operator<<(signed short ) { std::cout<<"signed short "<<std::endl; return *this; }
C>> S& operator<<(signed int ) { std::cout<<"signed int "<<std::endl; return *this; }
C>> S& operator<<(signed long ) { std::cout<<"signed long "<<std::endl; return *this; }
C>> S& operator<<(signed long long ) { std::cout<<"signed long long "<<std::endl; return *this; }
C>> S& operator<<(unsigned short ) { std::cout<<"unsigned short "<<std::endl; return *this; }
C>> S& operator<<(unsigned int ) { std::cout<<"unsigned int "<<std::endl; return *this; }
C>> S& operator<<(unsigned long ) { std::cout<<"unsigned long "<<std::endl; return *this; }
C>> S& operator<<(unsigned long long) { std::cout<<"unsigned long long"<<std::endl; return *this; }
C>>
C>>Суть: типы short и int (char и short, int и long, long и long long) могут иметь одинаковый размер, оставаясь разными типами. Тяжкое наследие Си. C>>Типы с фиксированным размером (std::intXX_t), ссылаются на один из этих типов. C>>Можно представить, что компилятор будет поддерживать какой-то специальный __int64_t, и std::int64_t ссылается на него, но я такого не встречал.
M>Да, в итоге так и сделал
И это правильное решение. Ибо всё это разные типы, по которым всегда можно сделать перегрузку. В то время как типы intN_t/uintN_t — это алиасы с недетерминированным определением: https://timsong-cpp.github.io/cppwp/cstdint.syn#1.
В то же время я считаю, что ты напрасно отбрасываешь вариант с шаблонами. С шаблонами можно сделать все по-красоте. Обрати внимание на возможность перегрузок с приоритетами. Т.е. для каких-то групп типов можно предоставить общую перегрузку, а какие-то группы типов обработать более специальным образом:
R>И это правильное решение. Ибо всё это разные типы, по которым всегда можно сделать перегрузку. В то время как типы intN_t/uintN_t — это алиасы с нерегламентированным определением: https://timsong-cpp.github.io/cppwp/cstdint.syn#1.
Да, я сначала не очень хорошо подумал, когда делал через std::u/intXX_t
Казалось красиво и единообразно, и по идее, должно было покрывать всё.
R>В то же время, я считаю, что ты напрасно отбрасываешь вариант с шаблонами. С шаблонами можно сделать все по красоте. Обрати внимание на возможность перегрузок с приоритетами. Т.е. для каких-то групп типов можно предоставить общую перегрузку, а какие-то группы типов обработать поотдельности:
Ну, у меня и другие типы, кроме интов используются, и, если говорить о других местах, то там и так шаблоны, не хотелось делать что у части шаблонов список шаблонных параметров другой. Плюс, мне надо чтобы работало ADL, с шаблонами может что-то сломаться (предполагается, что пользователь может использовать свои типы, и должен в своём NS определить для типа шаблонную функцию martyFormatValueFormat, где параметр шаблона тип строки, в которую надо конвертнуть значение пользовательского типа).
А по красоте с разными группами типов как раз особенно и не надо городить.
Здравствуйте, Marty, Вы писали:
M>Ну, у меня и другие типы, кроме интов используются, и, если говорить о других местах, то там и так шаблоны, не хотелось делать что у части шаблонов список шаблонных параметров другой.
По-моему, это довод в пользу использования шаблонов, а не отказа от них. Шаблоны же для того и существуют, чтобы помогать работать с разнообразыми системами типов эффективно и без дублирования кода.
M>Плюс, мне надо чтобы работало ADL, с шаблонами может что-то сломаться (предполагается, что пользователь может использовать свои типы, и должен в своём NS определить для типа шаблонную функцию martyFormatValueFormat, где параметр шаблона тип строки, в которую надо конвертнуть значение пользовательского типа).
Шаблоны сами по себе не ломают ADL. ADL — это про то, где и как компилятор будет искать кандидата для подстановки, а не про то, как кандидаты определены. ADL же отталикивается от типов фактических параметров. Поломаться если и может, то только от кривых рук.
--
Справедливость выше закона. А человечность выше справедливости.
_>>template<typename T> using is_bool = std::is_same<std::remove_cv_t<T>, bool>;
_>>
M>Спасибо!
Здесь уместно будет заюзать std::decay дабы полностью очистить тип от разного рода модификаторов, в т.ч. и ссылок, и тем самым избавиться от лишнего геморроя при работе с формальными параметрами функций:
template<typename T> using is_bool = std::is_same<std::decay_t<T>, bool>;
А ещё все вот эти метафунции std::is_... канули в прошлое с приходом концептов. То же самое, но по-современному:
template<typename T> concept Bool = std::same_as<std::decay_t<T>, bool>;
static_assert(Bool<const bool&>);
void foo(Bool auto b) {/* . . . */}
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>А ещё все вот эти метафунции std::is_... канули в прошлое с приходом концептов. То же самое, но по-современному:
Не не, пока только C++17 по многим причинам (в тч и потому, что в тех же линупсах дефолтный компилер не самый свежий)
А SFINAE был уже в C++03, но я тогда не прочувствовал его в полной мере, сам тогда такого кода почти не писал, только использовал, часто не понимая, как оно работает, да и хелперов из C++11 — C++17 не было, сейчас хочу прочувствовать всё это полной ложкой
Я не на переднем крае (cutting edge), я в своё время долго писал на C++03, даже после выхода C++11, а) бесовщина какая-то, б) ждал пока станет буль менее мейнстримом в) да и вообще часто другими вещами занимался, сипипи был в фоне
Здравствуйте, Marty, Вы писали:
M>А SFINAE был уже в C++03, но я тогда не прочувствовал его в полной мере, сам тогда такого кода почти не писал, только использовал, часто не понимая, как оно работает, да и хелперов из C++11 — C++17 не было, сейчас хочу прочувствовать всё это полной ложкой
Ты просто возьми себе на заметку, что концепты имеют существенные преимущества перед SFINAE. Одним из главных преимуществ я бы назвал Partial ordering. При использовании SFINAE все перегрузки равноправны и необходимо обеспечить, чтоб все фильтры были взаимоисключающими во избежание конфликтов. При большом количестве перегрузок в таких областях как ввод-вывод, сериализация, форматирование и т.п. это иногда превращается в нехилый геморрой. Использование концептов же позволяет делать одни перегрузки более специальными, чем другие. Это очень похоже на то, как если бы мы писали перегрузки для базового и производного классов.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Ты просто возьми себе на заметку, что концепты имеют существенные преимущества перед SFINAE. Одним из главных преимуществ я бы назвал Partial ordering. При использовании SFINAE все перегрузки равноправны и необходимо обеспечить, чтоб все фильтры были взаимоисключающими во избежание конфликтов. При большом количестве перегрузок в таких областях как ввод-вывод, сериализация, форматирование и т.п. это иногда превращается в нехилый геморрой. Использование концептов же позволяет делать одни перегрузки более специальными, чем другие. Это очень похоже на то, как если бы мы писали перегрузки для базового и производного классов.
Да, спасибо, принял, буду иметь в виду. Но я пока на C++17, таки.
У меня есть проекты, которые должны, по моим представлениям, запускаться и на W2K, или хотя бы WinXP. А последняя MSVS — 2019, которая умеет в WinXP — умеет только в C++17
Ну и откушать SFINAE полной ложкой... Мои загоны...
Здравствуйте, kov_serg, Вы писали:
_>вот что харектерно, c++ всё есть для того чтобы иметь bigint но std::bigint нет. _>Зато еще есть куча всяких целых спец типов __m128i __m256i __m512i
Когда решат как это сделать, тогда видимо и появится.
Здравствуйте, _NN_, Вы писали: _>>вот что харектерно, c++ всё есть для того чтобы иметь bigint но std::bigint нет. _>>Зато еще есть куча всяких целых спец типов __m128i __m256i __m512i _NN>Когда решат как это сделать, тогда видимо и появится.
Давно надо было это сделать. Чего тянут не понятно.
а то с++ как правило делает так:
#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>
using namespace boost::multiprecision;
int main() {
cpp_int u=1;
for(unsigned i=2;i<=100;i++) u*=i;
std::cout<<u<<std::endl;
return 0;
}
результат для 100!
In file included from example.cpp:2:
/usr/include/boost/multiprecision/cpp_int.hpp: In instantiation of ‘struct boost::multiprecision::backends::cpp_int_base<0, 4294967295, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, std::allocator<long long unsigned int>, false>’:
/usr/include/boost/multiprecision/cpp_int.hpp:1033:8: required from ‘struct boost::multiprecision::backends::cpp_int_backend<>’
1033 | struct cpp_int_backend
| ^~~~~~~~~~~~~~~
/usr/include/boost/multiprecision/number.hpp:1671:12: required from ‘class boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<> >’
1671 | Backend m_backend;
| ^~~~~~~~~
example:6:12: required from here
6 | cpp_int u=1;
| ^
/usr/include/boost/multiprecision/cpp_int.hpp:172:8: error: no class template named ‘rebind’ in ‘class std::allocator<long long unsigned int>’
172 | struct cpp_int_base<MinBits, MaxBits, signed_magnitude, Checked, Allocator, false> : private Allocator::template rebind<limb_type>::other
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/multiprecision/cpp_int.hpp:174:66: error: no class template named ‘rebind’ in ‘class std::allocator<long long unsigned int>’
174 | typedef typename Allocator::template rebind<limb_type>::other allocator_type;
| ^~~~~~~~~~~~~~