Страницы

Поиск по вопросам

вторник, 30 октября 2018 г.

Определение полного типа, назначаемого компилятором через auto

#include
using namespace std;
int main() { auto l = 4; cout << l << endl; return 0; }
Какой полный тип установит компилятор для l?
[const] short|int|long [signed|unsigned]
Как это определить?


Ответ

Есть замечательный ответ от Howard Hinnant на этот счёт на enSO, и я решил здесь предоставить его перевод с некоторыми упрощениями.

Для начала стоит сказать, что вариант использования typeid(a).name(), где a имя переменной, достаточно хорош.
Но в C++11 появился decltype(x), который превращает выражение в тип. И decltype() идёт вместе со своим набором интересных правил. Например, decltype(a) и decltype((a)) будут обычно давать различные типы.
Может ли наш надёжный typeid(a).name() помочь нам исследовать этот прекрасный новый мир?
Нет.
Но инструмент, который может, не так уж и сложен. И этот инструмент я использую в качестве ответа на этот вопрос. Я сравню и покажу различия между этим новым инструментом и typeid(a).name(). И этот новый инструмент фактически построен поверх typeid(a).name()
Фундаментальные проблемы:
typeid(a).name()
отбрасывает cv-квалификаторы, ссылки, и lvalue/rvalue-шность. Например:
const int ci = 0; std::cout << typeid(ci).name() << '
';
Для меня выводит:
i
и я предполагаю, что MSVC будет выводить:
int
Т.е. const пропал. Это происходит не из-за плохого качества реализации. Стандарт диктует такое поведение.
То, что я рекомендую далее, это:
template std::string type_name();
которое может быть использовано так:
const int ci = 0; std::cout << type_name() << '
';
и для меня выводит:
int const
C++11 решение
Я использую __cxa_demangle для не-MSVC платформ для расшифровки типов. Но для MSVC я доверяюсь typeid здесь (не тестировалось). И эта сущность обёрнута вокруг некоторого простого теста, который определяет, восстанавливает и выводит cv-квалификаторы и ссылки для входного типа.
#include #include #ifndef _MSC_VER # include #endif #include #include #include
template std::string type_name() { typedef typename std::remove_reference::type TR; std::unique_ptr own ( #ifndef _MSC_VER abi::__cxa_demangle(typeid(TR).name(), nullptr, nullptr, nullptr), #else nullptr, #endif std::free ); std::string r = own != nullptr ? own.get() : typeid(TR).name(); if (std::is_const::value) r += " const"; if (std::is_volatile::value) r += " volatile"; if (std::is_lvalue_reference::value) r += "&"; else if (std::is_rvalue_reference::value) r += "&&"; return r; }
Результаты
С помощью данного решения я могу сделать следующее:
int& foo_lref(); int&& foo_rref(); int foo_value();
int main() { int i = 0; const int ci = 0; std::cout << "decltype(i) is " << type_name() << '
'; std::cout << "decltype((i)) is " << type_name() << '
'; std::cout << "decltype(ci) is " << type_name() << '
'; std::cout << "decltype((ci)) is " << type_name() << '
'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '
'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '
'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '
'; std::cout << "decltype(foo_lref()) is " << type_name() << '
'; std::cout << "decltype(foo_rref()) is " << type_name() << '
'; std::cout << "decltype(foo_value()) is " << type_name() << '
'; }
и вывод получается такой:
decltype(i) is int decltype((i)) is int& decltype(ci) is int const decltype((ci)) is int const& decltype(static_cast(i)) is int& decltype(static_cast(i)) is int&& decltype(static_cast(i)) is int decltype(foo_lref()) is int& decltype(foo_rref()) is int&& decltype(foo_value()) is int
Расскажу (для примера) о разнице между decltype(i) и decltype((i)). Первое - это тип объявления для i. Последнее - это "тип" выражения i. (выражения никогда не имеют ссылочный тип, но по соглашению decltype представляет lvalue выражения с lvalue ссылками).
Таким образом, данный инструмент превосходное средство для изучения decltype, в добавок к исследованию и отладке вашего собственного кода.
Для сравнения, если я соберу это на базе typeid(a).name(), без добавления потерянных cv-квалификаторов или ссылок, результат будет такой:
decltype(i) is int decltype((i)) is int decltype(ci) is int decltype((ci)) is int decltype(static_cast(i)) is int decltype(static_cast(i)) is int decltype(static_cast(i)) is int decltype(foo_lref()) is int decltype(foo_rref()) is int decltype(foo_value()) is int
Т.е. каждая ссылка и cv-квалификатор срезаны.
C++14
Данный вариант решения обладает парой особенностей:
Он выполняется во время компиляции! Компилятор сам делает работу вместо библиотеки (даже вместо стандартной библиотеки). Это означает более точные результаты для более свежих языковых фич (таких как лямбды).

#include #include #include #include
#ifndef _MSC_VER # if __cplusplus < 201103 # define CONSTEXPR11_TN # define CONSTEXPR14_TN # define NOEXCEPT_TN # elif __cplusplus < 201402 # define CONSTEXPR11_TN constexpr # define CONSTEXPR14_TN # define NOEXCEPT_TN noexcept # else # define CONSTEXPR11_TN constexpr # define CONSTEXPR14_TN constexpr # define NOEXCEPT_TN noexcept # endif #else // _MSC_VER # if _MSC_VER < 1900 # define CONSTEXPR11_TN # define CONSTEXPR14_TN # define NOEXCEPT_TN # elif _MSC_VER < 2000 # define CONSTEXPR11_TN constexpr # define CONSTEXPR14_TN # define NOEXCEPT_TN noexcept # else # define CONSTEXPR11_TN constexpr # define CONSTEXPR14_TN constexpr # define NOEXCEPT_TN noexcept # endif #endif // _MSC_VER
class static_string { const char* const p_; const std::size_t sz_;
public: typedef const char* const_iterator;
template CONSTEXPR11_TN static_string(const char(&a)[N]) NOEXCEPT_TN : p_(a) , sz_(N-1) {}
CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN : p_(p) , sz_(N) {}
CONSTEXPR11_TN const char* data() const NOEXCEPT_TN {return p_;} CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN {return sz_;}
CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN {return p_;} CONSTEXPR11_TN const_iterator end() const NOEXCEPT_TN {return p_ + sz_;}
CONSTEXPR11_TN char operator[](std::size_t n) const { return n < sz_ ? p_[n] : throw std::out_of_range("static_string"); } };
inline std::ostream& operator<<(std::ostream& os, static_string const& s) { return os.write(s.data(), s.size()); }
template CONSTEXPR14_TN static_string type_name() { #ifdef __clang__ static_string p = __PRETTY_FUNCTION__; return static_string(p.data() + 31, p.size() - 31 - 1); #elif defined(__GNUC__) static_string p = __PRETTY_FUNCTION__; # if __cplusplus < 201402 return static_string(p.data() + 36, p.size() - 36 - 1); # else return static_string(p.data() + 46, p.size() - 46 - 1); # endif #elif defined(_MSC_VER) static_string p = __FUNCSIG__; return static_string(p.data() + 38, p.size() - 38 - 7); #endif }
Данный код будет автоматически заботиться о constexpr, если вы до сих пор застряли в древнем C++11. А если вы рисуете на стенах пещер с помощью C++98/03, то noexcept также будет принесён в жертву.
C++17
Новый стандартный класс std::string_view может заменить рукописный static_string
template constexpr std::string_view type_name() { using namespace std; #ifdef __clang__ string_view p = __PRETTY_FUNCTION__; return string_view(p.data() + 34, p.size() - 34 - 1); #elif defined(__GNUC__) string_view p = __PRETTY_FUNCTION__; # if __cplusplus < 201402 return string_view(p.data() + 36, p.size() - 36 - 1); # else return string_view(p.data() + 46, p.size() - 46 - 1); # endif #elif defined(_MSC_VER) string_view p = __FUNCSIG__; return string_view(p.data() + 38, p.size() - 38 - 7); #endif }

Комментариев нет:

Отправить комментарий