В C++ строка форматирования опять Часть-3


Ранее спрашивал здесь.

Код теперь доступен на Гитхабе.

После предыдущего обзора я добавил юнит-тесты.

Поскольку он большой, он будет приходить на пару частей.

Часть 1 | Часть 2 | Часть 3 | Часть 4

Часть 3

Это когда вся грязная работа выполняется. Когда apply() называется этот объект форматах объекта в соответствии с info (FormatInfo) на потоке.

В apply() мы делаем несколько основных типа проверки и разрешить некоторые простые преобразования входных параметров. Примечание в C (Чара, короче) преобразуются к int при передаче в качестве параметров, так что некоторые взлом вокруг, что делается для компенсации в C++.

После того, как разрешил преобразования были применены соответствующие трансляция форматирования функций, чтобы поместить поток в правильное состояние, то мы называем printToStream()после этого завершает поток сбрасывается в исходное состояние.

Примечание: Вы можете думать о printToStream() как s << arg;

В части 4 вы увидите, как это не совсем верно.

Праматерия.ч

#ifndef THORSANVIL_IOUTIL_FORMATTER_H
#define THORSANVIL_IOUTIL_FORMATTER_H

#include "printToStream.h"
#include "saveToStream.h"
#include "FormatInfo.h"
#include "SignConversionOption.h"

#include <ostream>
#include <string>
#include <map>
#include <exception>
#include <stdexcept>
#include <typeindex>
#include <cassert>
#include <type_traits>


namespace ThorsAnvil::IOUtil
{


template<typename T>
inline bool checkNumLargerEqualToZero(T const& value)      {return value >= 0;}
inline bool checkNumLargerEqualToZero(char const*)         {return false;}

class Formatter
{
    // The number of characters read in the formatter.
    std::size_t             used;
    // If this object reads the width/precision from the next parameter
    // If this value is Dynamic::Width or Dynamic::Precision then  info is not used.
    Dynamic                 dynamicSize;

    // Details extracted from the format string.
    FormatInfo              info;

    // When you apply a `Formatter` object to a stream this temporary object is created.
    // When the actual object is then applied to this object we call back to the Formatter::apply()
    // method to do the actual work of setting up the stream and printing the value. When it is
    // all done we return the original stream.
    // see below friend FormatterCheck operator<<(std::ostream&, Format const&)
    // Usage:
    //      stream << FormatObject << value;
    //      Notes:
    //          stream << FormatObject          returns a FormatChecker
    //          FormatChecker << value          calls apply()
    //                                          the returns the original stream
    struct FormatterCheck
    {
        std::ostream&       stream;
        Formatter const&    formatter;
        FormatterCheck(std::ostream& s, Formatter const& formatter)
            : stream(s)
            , formatter(formatter)
        {}
        template<typename A>
        std::ostream& operator<<(A const& nextArg)
        {
            formatter.apply(stream, nextArg);
            return stream;
        }
    };
    public:
        /* The constructor reads a string
         * and sets up all the data into info
         * Unless we find a Dynamic Width/Precision
         * then dynamicSize is updated and we return immediately indicating zero size.
         * The Format constructor will then call again to get the actual formatter object.
         */
       Formatter(char const* formatStr, Dynamic dynamicWidthHandeled)
            : used(0)
            , dynamicSize(Dynamic::None)
            , info()
        {
            // precision of 0 is a special case.
            // We need to know if the precision has not been specified at all.
            info.precision = -1;

            // Scan the format string to set up all the
            // the member variables.
            char const* fmt = formatStr;

            // Must start with a '%'
            assert(*fmt == '%');

            bool flag = true;
            // Scan the flags.
            // There can be more than one. So loop until we don't find a flag.
            do
            {
                ++fmt;
                switch (*fmt)
                {
                    case '-':   info.leftJustify     = true;break;
                    case '+':   info.forceSign       = true;break;
                    case ' ':   info.forceSignWidth  = true;break;
                    case '#':   info.prefixType      = true;break;
                    case '0':   info.leftPad         = true;break;
                    default:    flag = false;
                }
            } while (flag);

            // Check to see if there is a width.
            if (std::isdigit(*fmt))
            {
                char* end;
                info.width = std::strtol(fmt, &end, 10);
                fmt = end;
            }
            else if (*fmt == '*')
            {
                // Dynamic Width
                if (dynamicWidthHandeled == Dynamic::None)
                {
                    // We have not previously processed it.
                    // So this object becomes the Formatter to handle
                    // the dynamic Width and we return immediately.
                    dynamicSize         = Dynamic::Width;
                    info                = FormatInfo();
                    return;
                }
                // If we get here then the have previously handled this field.
                ++fmt;
            }

            // Check to see if there is a precision
            if (*fmt == '.')
            {
                ++fmt;
                if (std::isdigit(*fmt))
                {
                    char* end;
                    info.precision = std::strtol(fmt, &end, 10);
                    fmt = end;
                }
                else if (*fmt == '*')
                {
                    // We have not previously processed it.
                    // So this object becomes the Formatter to handle
                    // the dynamic Width and we return immediately.
                    if (dynamicWidthHandeled == Dynamic::None || dynamicWidthHandeled == Dynamic::Width)
                    {
                        dynamicSize         = Dynamic::Precision;
                        info                = FormatInfo();
                        return;
                    }
                    // If we get here then the have previously handled this field.
                    ++fmt;
                }
                else
                {
                    // The actual value is not required (just the dot).
                    // If there is no value precision is 0 (rather than default)
                    info.precision = 0;
                }
            }

            // Check for the length
            // This converts from int to long int etc.
            // note this is optional.
            char first = *fmt;
            ++fmt;
            switch (first)
            {
                case 'h':   info.length = Length::h;
                            if (*fmt == 'h')
                            {
                                ++fmt;
                                info.length  = Length::hh;
                            }
                            break;
#pragma vera-pushoff
                case 'l':   info.length = Length::l;
                            if (*fmt == 'l')
                            {
                                ++fmt;
                                info.length  = Length::ll;
                            }
                            break;
#pragma vera-pop
                case 'j':   info.length = Length::j;break;
                case 'z':   info.length = Length::z;break;
                case 't':   info.length = Length::t;break;
                case 'L':   info.length = Length::L;break;
                default:
                    --fmt;
            }

            // Check for the specifier value.
            switch (*fmt)
            {
                case 'd':   info.specifier = Specifier::d;info.type = Type::Int;      break;
                case 'i':   info.specifier = Specifier::i;info.type = Type::Int;      break;
                case 'u':   info.specifier = Specifier::u;info.type = Type::UInt;     break;
                case 'o':   info.specifier = Specifier::o;info.type = Type::UInt;     break;
                case 'x':   info.specifier = Specifier::x;info.type = Type::UInt;     break;
                case 'X':   info.specifier = Specifier::X;info.type = Type::UInt;     break;
                case 'f':   info.specifier = Specifier::f;info.type = Type::Float;    break;
                case 'F':   info.specifier = Specifier::F;info.type = Type::Float;    break;
                case 'e':   info.specifier = Specifier::e;info.type = Type::Float;    break;
                case 'E':   info.specifier = Specifier::E;info.type = Type::Float;    break;
                case 'g':   info.specifier = Specifier::g;info.type = Type::Float;    break;
                case 'G':   info.specifier = Specifier::G;info.type = Type::Float;    break;
                case 'a':   info.specifier = Specifier::a;info.type = Type::Float;    break;
                case 'A':   info.specifier = Specifier::A;info.type = Type::Float;    break;
                case 'c':   info.specifier = Specifier::c;info.type = Type::Char;     break;
                case 's':   info.specifier = Specifier::s;info.type = Type::String;   break;
                case 'p':   info.specifier = Specifier::p;info.type = Type::Pointer;  break;
                case 'n':   info.specifier = Specifier::n;info.type = Type::Count;    break;
                default:
                    // Not optional so throw if we don't find it.
                   throw std::invalid_argument(std::string("Invalid Parameter specifier: ") + *fmt);
            }
            ++fmt;

            // Feedback for the calling routine.
            // Now we know how much string was used to calculate the value.
            info.useDynamicSize = dynamicWidthHandeled;
            used  = fmt - formatStr;

            // Now we processes the information and set the formatter fields used by streams.
            // Pre-calculate the type information of the next argument.
            info.expectedType = getType(info.length, info.type);

            // Pre-calculate the format flags that will be used to set up the stream.
            info.format  |= (info.leftJustify ? std::ios_base::left : std::ios_base::right);

            // Are we expecting a number type?
            // Set dec/oct/hex/fixed/scientific
            if (info.specifier == Specifier::d || info.specifier == Specifier::i)
            {
                info.format  |= std::ios_base::dec;
            }
            else if (info.specifier == Specifier::o)
            {
                info.format  |= std::ios_base::oct;
            }
            else if (info.specifier == Specifier::x || info.specifier == Specifier::X)
            {
                info.format  |= std::ios_base::hex;
            }
            else if (info.specifier == Specifier::f || info.specifier == Specifier::F)
            {
                info.format |= std::ios_base::fixed;
            }
            else if (info.specifier == Specifier::e || info.specifier == Specifier::E)
            {
                info.format |= std::ios_base::scientific;
            }
            else if (info.specifier == Specifier::a || info.specifier == Specifier::A)
            {
                info.format |= (std::ios_base::fixed | std::ios_base::scientific);
            }

            // Some specifiers define if we are using upper case (rather than the default lowercase for any letters)
            if (info.specifier == Specifier::X || info.specifier == Specifier::F || info.specifier == Specifier::E || info.specifier == Specifier::A || info.specifier == Specifier::G)
            {
                info.format |= std::ios_base::uppercase;
            }

            // Show the base types for certain output specifiers.
            if (info.prefixType && (info.specifier == Specifier::o || info.specifier == Specifier::x || info.specifier == Specifier::X))
            {
                info.format |= std::ios_base::showbase;
            }

            // Show the floating point even if there is no fraction.
            if (info.prefixType && info.type == Type::Float)
            {
                info.format |= std::ios_base::showpoint;
            }

            // Show the '+' sign for positive values.
            if (info.forceSign && (info.type == Type::Float || info.type == Type::Int))
            {
                info.format |= std::ios_base::showpos;
            }
        }
        std::size_t size()          const {return used;}
        Dynamic     isDynamicSize() const {return dynamicSize;}

        // We pass the formatter to the stream first
        // So we create a marker object used to print the actual argument.
        // This will call apply() with the actual argument.
        friend FormatterCheck operator<<(std::ostream& s, Formatter const& formatter)
        {
            return FormatterCheck(s, formatter);
        }
        private:
            template<typename A>
            void apply(std::ostream& s, A const& arg) const
            {
                if (dynamicSize == Dynamic::None)
                {
                    using Actual       = typename SignConversionOption<A>::Actual;
                    using Alternative  = typename SignConversionOption<A>::Alternative;

                    if (std::type_index(typeid(Actual)) == std::type_index(*info.expectedType.first))
                    {
                        applyData(s, arg);
                    }
                    else if (std::type_index(typeid(Actual)) != std::type_index(typeid(Alternative)) && std::type_index(*info.expectedType.first) == std::type_index(typeid(Alternative)))
                    {
                        applyData(s, static_cast<Alternative const&>(arg));
                    }
                    else if (SignConversionOption<A>::allowIntConversion)
                    {
                        applyData(s, SignConversionOption<A>::convertToInt(arg));
                    }
                    else if (std::type_index(typeid(A)) == std::type_index(typeid(int)) && info.expectedType.second)
                    {
                        applyData(s, SignConversionOption<A>::truncate(arg, info.expectedType.second));
                    }
                    else
                    {
                        throw std::invalid_argument(std::string("Actual argument does not match supplied argument (or conversions): Expected(") + info.expectedType.first->name() + ") Got(" + typeid(A).name() + ")");
                    }
                }
                else
                {
                    if (std::type_index(typeid(A)) != std::type_index(typeid(int)))
                    {
                        throw std::invalid_argument("Dynamic Width of Precision is not an int");
                    }
                    saveToStream(s, dynamicSize, arg);
                }
            }

            template<typename A>
            void applyData(std::ostream& s, A const& arg) const
            {
                // Fill is either 0 or space and only used for numbers.
                char        fill      = (!info.leftJustify && info.leftPad) ? '0' : ' ';
                std::size_t fillWidth = (info.useDynamicSize == Dynamic::Width || info.useDynamicSize == Dynamic::Both)
                                            ? std::abs(s.iword(static_cast<int>(Dynamic::Width)))
                                            : info.width;
                std::size_t fractPrec = (info.useDynamicSize == Dynamic::Precision || info.useDynamicSize == Dynamic::Both)
                                            ? s.iword(static_cast<int>(Dynamic::Precision))
                                            : info.precision == -1 && info.type == Type::Float ? 6 : info.precision;
                bool                    forceLeft = info.leftJustify;
                std::ios_base::fmtflags format    = info.format;
                if ((info.useDynamicSize == Dynamic::Width || info.useDynamicSize == Dynamic::Both) && s.iword(static_cast<int>(Dynamic::Width)) < 0)
                {
                    forceLeft   = true;
                    format  |=  std::ios_base::left;
                    format  &=  ~std::ios_base::right;

                }

                // Take special care if we forcing a space in-front of positive values.
                if (info.forceSignWidth && !info.forceSign && checkNumLargerEqualToZero(arg) && (info.type == Type::Float || info.type == Type::Int))
                {
                    s << ' ';
                    fillWidth = fillWidth == 0 ? 0 : fillWidth - 1;
                }

                // Set up the stream for formatting
                auto oldFlags = s.flags(format);
                auto oldFill  = s.fill(fill);
                auto oldWidth = s.width(fillWidth);
                auto oldPrec  = s.precision(fractPrec);

                FormatInfo  formatInfo  = info;
                formatInfo.width        = fillWidth;
                formatInfo.precision    = fractPrec;
                formatInfo.leftJustify  = forceLeft;
                printToStream(s, arg, formatInfo);

                // reset the stream to original state
                s.precision(oldPrec);
                s.width(oldWidth);
                s.fill(oldFill);
                s.flags(oldFlags);
            }
            // Only certain combinations of Specifier and Length are supported.
            static AllowedType getType(Length length, Type type)
            {
                static std::map<std::pair<Type, Length>, AllowedType>    typeMap =
                {
#pragma vera-pushoff
                    {{Type::Int,   Length::none}, {&typeid(int), 0}},
                    {{Type::Int,   Length::hh},   {&typeid(signed char), 0xFF}},
                    {{Type::Int,   Length::h},    {&typeid(short int), 0xFFFF}},
                    {{Type::Int,   Length::l},    {&typeid(long int), 0}},
                    {{Type::Int,   Length::ll},   {&typeid(long long int), 0}},
                    {{Type::Int,   Length::j},    {&typeid(std::intmax_t), 0}},
                    {{Type::Int,   Length::z},    {&typeid(std::size_t), 0}},
                    {{Type::Int,   Length::t},    {&typeid(std::ptrdiff_t), 0}},

                    {{Type::UInt,  Length::none}, {&typeid(unsigned int), 0}},
                    {{Type::UInt,  Length::hh},   {&typeid(unsigned char), 0xFF}},
                    {{Type::UInt,  Length::h},    {&typeid(unsigned short int), 0xFFFF}},
                    {{Type::UInt,  Length::l},    {&typeid(unsigned long int), 0}},
                    {{Type::UInt,  Length::ll},   {&typeid(unsigned long long int), 0}},
                    {{Type::UInt,  Length::j},    {&typeid(std::intmax_t), 0}},
                    {{Type::UInt,  Length::z},    {&typeid(std::size_t), 0}},
                    {{Type::UInt,  Length::t},    {&typeid(std::ptrdiff_t), 0}},

                    {{Type::Float, Length::none}, {&typeid(double), 0}},       {{Type::Float, Length::l}, {&typeid(double), 0}},          {{Type::Float, Length::L}, {&typeid(long double), 0}},
                    {{Type::Char,  Length::none}, {&typeid(int), 0}},          {{Type::Char,  Length::l}, {&typeid(std::wint_t), 0}},
                    {{Type::String,Length::none}, {&typeid(char const*), 0}},  {{Type::String,Length::l}, {&typeid(wchar_t const*), 0}},

                    {{Type::Pointer,Length::none},{&typeid(void*), 0}},

                    {{Type::Count, Length::none}, {&typeid(int*), 0}},
                    {{Type::Count, Length::hh},   {&typeid(signed char*), 0}},
                    {{Type::Count, Length::h},    {&typeid(short int*), 0}},
                    {{Type::Count, Length::l},    {&typeid(long int*), 0}},
                    {{Type::Count, Length::ll},   {&typeid(long long int*), 0}},
                    {{Type::Count, Length::j},    {&typeid(std::intmax_t*), 0}},
                    {{Type::Count, Length::z},    {&typeid(std::size_t*), 0}},
                    {{Type::Count, Length::t},    {&typeid(std::ptrdiff_t*), 0}}
#pragma vera-pop
                };
                auto find = typeMap.find({type, length});
                if (find == typeMap.end())
                {
                    throw std::invalid_argument("Specifier and length are not a valid combination");
                }
                return find->second;
            }
};

}

#endif

SignConversionOption

#ifndef THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
#define THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H

namespace ThorsAnvil::IOUtil
{

/*
 * When handling integer types some
 * automatic conversions are allowed.
 *
 * This type handles these conversions.
 * It is used by Formatter::apply()
 */
template<typename T>
struct SignConversionOption
{
    using Actual        = T;                                    // The Current Type
    using Alternative   = T;                                    // Acceptable alternative type we can cast from
    static constexpr bool allowIntConversion = false;           // Can we convert this type from int by call convertToInt()
    static int convertToInt(T const&) {return 0;}
    static int truncate(T const& arg, int mask) {return 0;};    // Int only we truncate the value by masking if top bits.
                                                                // The mask is retrieved from Formatter::getType()
};

template<>
struct SignConversionOption<char>
{
    using Actual        = char;
    using Alternative   = unsigned char;
    static constexpr bool allowIntConversion = true;
    static int convertToInt(char const& arg) {return arg;}
    static int truncate(char const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<short>
{
    using Actual        = short;
    using Alternative   = unsigned short;
    static constexpr bool allowIntConversion = true;
    static int convertToInt(short const& arg) {return arg;}
    static int truncate(short const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<int>
{
    using Actual        = int;
    using Alternative   = unsigned int;
    static constexpr bool allowIntConversion = false;
    static int convertToInt(int const&) {return 0;}
    static int truncate(int const& arg, int mask) {return arg & mask;};
};
template<>
struct SignConversionOption<long>
{
    using Actual        = long;
    using Alternative   = unsigned long;
    static constexpr bool allowIntConversion = false;
    static int convertToInt(long const&) {return 0;}
    static int truncate(long const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<long long>
{
    using Actual        = long long;
    using Alternative   = unsigned long long;
    static constexpr bool allowIntConversion = false;
    static int convertToInt(long long const&) {return 0;}
    static int truncate(long long const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<unsigned char>
{
    using Actual        = unsigned char;
    using Alternative   = char;
    static constexpr bool allowIntConversion = true;
    static int convertToInt(unsigned char const& arg) {return arg;}
    static int truncate(unsigned char const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<unsigned short>
{
    using Actual        = unsigned short;
    using Alternative   = short;
    static constexpr bool allowIntConversion = true;
    static int convertToInt(unsigned short const& arg) {return arg;}
    static int truncate(unsigned short const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<unsigned int>
{
    using Actual        = unsigned int;
    using Alternative   = int;
    static constexpr bool allowIntConversion = false;
    static int convertToInt(unsigned int const&) {return 0;}
    static int truncate(unsigned int const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<unsigned long>
{
    using Actual        = unsigned long;
    using Alternative   = long;
    static constexpr bool allowIntConversion = false;
    static int convertToInt(unsigned long const&) {return 0;}
    static int truncate(unsigned long const& arg, int mask) {return 0;};
};
template<>
struct SignConversionOption<unsigned long long>
{
    using Actual        = unsigned long long;
    using Alternative   = long long;
    static constexpr bool allowIntConversion = false;
    static int convertToInt(unsigned long long const&) {return 0;}
    static int truncate(unsigned long long const& arg, int mask) {return 0;};
};

}

#endif

SaveToStream

#ifndef THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#define THORSANVIL_IOUTIL_SAVE_TO_STREAM_H

#include "FormatInfo.h"
#include <iostream>

namespace ThorsAnvil::IOUtil
{

template<typename T>
inline void saveToStream(std::ostream&, Dynamic, T const&)
{}

template<>
inline void saveToStream(std::ostream& s, Dynamic pos, int const& size)
{
    s.iword(static_cast<int>(pos)) = size;
}

}

#endif


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

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

Уменьшение дублирования кода

SignConversionOption имеет большое количество дублирования кода. Шаблоны, как правило, отобрать дублирования:

namespace details
{
template <typename T, bool is_eligible>
struct alternative_type {
using type = T;
};

template <typename Integral>
struct alternative_type<Integral, false> {
using type = std::conditional_t<std::is_signed_v<Integral>,
std::make_unsigned_t<Integral>, std::make_signed<Integral>>;
};

template <typename T, bool is_eligible>
using alternative_type_t = typename alternative_type<T, is_eligible>::type;

template <typename T, typename ... Types>
static constexpr bool is_one_of = (std::is_same_v<T, Types> || ...);
}

template <typename T>
struct sign_conversion_behavior
{
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;

static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;

using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;

static constexpr int convert_to_int(const T& val)
{
return do_convert(std::bool_constant<is_convertible>{}, val);
}

static constexpr int truncate(const int& arg, int mask) {return arg & mask;}

template <typename U>
static constexpr int truncate(const U&, int) {return 0;}

private:
static constexpr int do_convert(std::true_type, const T& val) {return val;}
static constexpr int do_convert(std::false_type, const T&) {return 0;}
};

Один-подкладка шаблоны могут сделать ее еще короче, но я надеюсь, что форматирование приведет интуиция, тот, кто читает это.

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

Объяснение


  • alternative_type

Логика немного вложенным. Если тип не является принципиальным или не цельный, просто использовать настройки по умолчанию. Если основополагающим и неотъемлемым, решить, основанные на знак.


  • is_one_of

Довольно простым. Постарайтесь, чтобы соответствовать T против всех Typesпо одному.


  • convert_to_int

Основными тегами отправки. Попытка SFINAE не будет работать, проверено. std::true_type это std::bool_constant<true>и то же самое для std::false_type. "Если у вас нет никаких других идей, использование меченых рассылка". Кто-то сказал мне это.


  • truncate

Кусок торта. Перегрузка и тот факт, что нешаблонность всегда имеет более высокий приоритет в разрешении перегрузки.

Тестирование

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

Полный код:

#include <type_traits>
#include <utility>
#include <stdexcept>
#include <typeinfo>
#include <string>

//from https://stackoverflow.com/a/4541470/4593721
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

std::string demangle(const char* name) {

int status = -4; // some arbitrary value to eliminate the compiler warning

// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};

return (status==0) ? res.get() : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
return name;
}
#endif

namespace details
{
template <typename T, bool is_eligible>
struct alternative_type {
using type = T;
};

template <typename Integral>
struct alternative_type<Integral, false> {
using type = std::conditional_t<std::is_signed_v<Integral>,
std::make_unsigned_t<Integral>, std::make_signed<Integral>>;
};

template <typename T, bool is_eligible>
using alternative_type_t = typename alternative_type<T, is_eligible>::type;

template <typename T, typename ... Types>
static constexpr bool is_one_of = (std::is_same_v<T, Types> || ...);
}

template <typename T>
struct sign_conversion_behavior
{
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;

static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;

using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;

static constexpr int convert_to_int(const T& val)
{
return do_convert(std::bool_constant<is_convertible>{}, val);
}

static constexpr int truncate(const int& arg, int mask) {return arg & mask;}

template <typename U>
static constexpr int truncate(const U&, int) {return 0;}

private:
static constexpr int do_convert(std::true_type, const T& val) {return val;}
static constexpr int do_convert(std::false_type, const T&) {return 0;}
};

template <typename TestType, typename Alternative, bool should_convert>
void test_func(const TestType& value)
{
using behavior = sign_conversion_behavior<TestType>;
static_assert(!std::is_same_v<typename behavior::alternative_type, Alternative>, "Alternative type deduction doesn't work");

if constexpr (should_convert)
{
if (static_cast<int>(value) != behavior::convert_to_int(value))
{
throw std::logic_error(std::string{"value conversion happens where it shouldn't "} + demangle(typeid(TestType).name()));
}
}
}

int main()
{
test_func<int, unsigned int, false>(1);
test_func<short, unsigned short, true>(1);

test_func<unsigned int, int, false>(1);
test_func<unsigned short, short, true>(1);
}

Жить на Wandbox.

Заключение

Новый код имеет очень мало дублирования кода. Это требование добавлено для C++17 (мягкие требования), и сделал код более сложным, чтобы понять. Я не уверен, если это-победа, но определенно заслуживает внимания. Это constexpr готова, хотя, который является довольно большим благом.

2
ответ дан 6 марта 2018 в 07:03 Источник Поделиться