Почему этот специализированный char_traits <uint8_t> и codecvt <uint8_t> для использования с шаблоном basic_ifstream выдает std:: bad_cast?

Там уже здесь, когда Stackoverflow запрашивает , почему basic_fstream<uint8_t> не работает. Ответы говорят, что char_traits специализируется только на char и wchar_t (плюс char16_t, char32_t в С++ 11), и вы должны придерживаться basic_fstream<char> для чтения двоичных данных и приведения их в случае необходимости.

Хорошо, черт побери, это не очень хорошо!:)

Ни один из ответов (я не могу найти) сказать , как, чтобы специализировать char_traits<uint8_t> и использовать его с шаблоном basic_fstream, или если это возможно. Поэтому я подумал, что попробую реализовать его сам.

Следующие компиляции без ошибок при использовании Visual Studio Express 2013 RC на Windows 7 64bit и с g++ - 4.7 на Kubuntu GNU/Linux 13.04 64bit. Однако при запуске он генерирует исключение std:: bad_cast. У меня нет доступа к clang++ с libС++ для проверки этой комбинации.

#include <cinttypes>
#include <cstring>

#include <algorithm>
#include <fstream>
#include <iostream>
#include <locale>

#ifdef _WIN32
    #define constexpr
    #define NOEXCEPT throw()
#else
    #define NOEXCEPT noexcept
#endif

// Change this to char and it works.
using byte_type = std::uint8_t;

namespace std
{
    // Specialization of std::char_traits
    template <> struct char_traits< std::uint8_t >
    {
        using char_type = std::uint8_t;
        using int_type = int;
        using off_type = std::streamoff;
        using pos_type = std::streampos;
        using state_type = std::mbstate_t;

        static void assign(char_type& value1, const char_type& value2)
        {
            value1 = value2;
        }

        static char_type* assign(char_type* ptr, std::size_t count, char_type value)
        {
            return static_cast<char_type*>(std::memset(ptr, value, count));
        }

        static constexpr bool eq(const char_type& value1, const char_type& value2) NOEXCEPT
        {
            return value1 == value2;
        }

        static constexpr bool lt(const char_type value1, const char_type value2) NOEXCEPT
        {
            return value1 < value2;
        }

        static std::size_t length(const char_type* ptr)
        {
            std::size_t i = 0;
            while (!eq(ptr[i], char_type()))
            {
                ++i;
            }
            return i;
        }

        static int compare(const char_type* ptr1, const char_type* ptr2, std::size_t count)
        {
            return std::memcmp(ptr1, ptr2, count);
        }

        static const char_type* find(const char_type* ptr, std::size_t count, const char_type& value)
        {
            return static_cast<const char_type*>(std::memchr(ptr, value, count));
        }

        static char_type* move(char_type* dest, const char_type* src, std::size_t count)
        {
            return static_cast<char_type*>(std::memmove(dest, src, count));
        }

        static char_type* copy(char_type* dest, const char_type* src, std::size_t count)
        {
            return static_cast<char_type*>(std::memcpy(dest, src, count));
        }

        static constexpr char_type to_char_type(const int_type& value) NOEXCEPT
        {
            return static_cast<char_type>(value);
        }

        static constexpr int_type to_int_type(const char_type& value) NOEXCEPT
        {
            return static_cast<int_type>(value);
        }

        static constexpr bool eq_int_type(const int_type& value1, const int_type& value2) NOEXCEPT
        {
            return value1 == value2;
        }

        static constexpr int_type eof() NOEXCEPT
        {
            return static_cast<int_type>(std::char_traits<char>::eof());
        }

        static constexpr int_type not_eof(const int_type& value) NOEXCEPT
        {
            return (value == eof()) ? 0 : value;
        }
    };

    // Specialization of std::codecvt
    template<> class codecvt< std::uint8_t, char, std::mbstate_t > : public locale::facet, public codecvt_base
    {
    public:
        using internal_type = std::uint8_t;
        using external_type = char;
        using state_type = std::mbstate_t;

        static std::locale::id id;

        codecvt(std::size_t refs = 0)
            : locale::facet(refs)
        {}

        std::codecvt_base::result out(state_type& state, const internal_type* from, const internal_type* from_end, const internal_type*& from_next, external_type* to, external_type* to_end, external_type*& to_next) const
        {
            return do_out(state, from, from_end, from_next, to, to_end, to_next);
        }

        std::codecvt_base::result in(state_type& state, const external_type* from, const external_type* from_end, const external_type*& from_next, internal_type* to, internal_type* to_end, internal_type*& to_next) const
        {
            return do_in(state, from, from_end, from_next, to, to_end, to_next);
        }

        std::codecvt_base::result unshift(state_type& state, external_type* to, external_type* to_end, external_type*& to_next) const
        {
            return do_unshift(state, to, to_end, to_next);
        }

        int length(state_type& state, const external_type* from, const external_type* from_end, std::size_t max) const
        {
            return do_length(state, from, from_end, max);
        }

        int max_length() const NOEXCEPT
        {
            return do_max_length();
        }

        int encoding() const NOEXCEPT
        {
            return do_encoding();
        }

        bool always_noconv() const NOEXCEPT
        {
            return do_always_noconv();
        }

    protected:
        virtual ~codecvt() {}
        virtual std::codecvt_base::result do_out(state_type& state, const internal_type* from, const internal_type* from_end, const internal_type*& from_next, external_type* to, external_type* to_end, external_type*& to_next) const;
        virtual std::codecvt_base::result do_in(state_type& state, const external_type* from, const external_type* from_end, const external_type*& from_next, internal_type* to, internal_type* to_end, internal_type*& to_next) const;
        virtual std::codecvt_base::result do_unshift(state_type& state, external_type* to, external_type* to_end, external_type*& to_next) const;
        virtual int do_length(state_type& state, const external_type* from, const external_type* from_end, std::size_t max) const;
        virtual int do_max_length() const NOEXCEPT;
        virtual int do_encoding() const NOEXCEPT;
        virtual bool do_always_noconv() const NOEXCEPT;
    }; // class codecvt

    locale::id codecvt< std::uint8_t, char, std::mbstate_t >::id;

    codecvt_base::result codecvt< std::uint8_t, char, std::mbstate_t >::do_out(state_type& state, const internal_type* from, const internal_type* from_end, const internal_type*& from_next, external_type* to, external_type* to_end, external_type*& to_next) const
    {
        (void) state; (void) from_end; (void) to_end; // Unused parameters
        from_next = from;
        to_next = to;
       return codecvt_base::noconv;
    }

    codecvt_base::result codecvt< std::uint8_t, char, std::mbstate_t >::do_in(state_type& state, const external_type* from, const external_type* from_end, const external_type*& from_next, internal_type* to, internal_type* to_end, internal_type*& to_next) const
    {
        (void) state; (void) from_end; (void) to_end; // Unused parameters
        from_next = from;
        to_next = to;
        return std::codecvt_base::noconv;
    }

    codecvt_base::result codecvt< std::uint8_t, char, std::mbstate_t >::do_unshift(state_type& state, external_type* to, external_type* to_end, external_type*& to_next) const
    {
        (void) state; (void) to_end; // Unused perameters
        to_next = to;
        return std::codecvt_base::noconv;
    }

    int codecvt< std::uint8_t, char, std::mbstate_t >::do_length(state_type& state, const external_type* from, const external_type* from_end, std::size_t max) const
    {
        (void) state; // Unused parameter
        return static_cast<int>(std::min< std::size_t >(max, static_cast<std::size_t>(from_end - from)));
    }

    int codecvt< std::uint8_t, char, std::mbstate_t >::do_max_length() const NOEXCEPT
    {
        return 1;
    }

    int codecvt< std::uint8_t, char, std::mbstate_t >::do_encoding() const NOEXCEPT
    {
        return 1;
    }

    bool codecvt< std::uint8_t, char, std::mbstate_t >::do_always_noconv() const NOEXCEPT
    {
        return true;
    }
} // namespace std


int main(int argc, char *argv [])
{
    if (argc < 2)
    {
        std::cerr << argv[0] << " {file to read}" << std::endl;
        return EXIT_FAILURE;
    }

    using stream_type = std::basic_ifstream< byte_type, std::char_traits<byte_type> >;

    stream_type stream(argv[1], std::ifstream::in | std::ifstream::binary);
    if (stream.is_open() == false)
    {
        std::cerr << "file not found" << std::endl;
        return EXIT_FAILURE;
    }
    stream.exceptions(std::ifstream::badbit);

    static const auto read_size = 4;
    stream_type::char_type buffer[read_size];

    stream.read(buffer, read_size);

    std::cout << "Got:" << stream.gcount() << std::endl;

    return EXIT_SUCCESS;
}

Скомпилировать и запустить с помощью g++ и GNU/Linux:

$ g++ -std=c++11 -Wall -Wextra -pedantic stream.cpp -o stream && ./stream /dev/random 
terminate called after throwing an instance of 'std::bad_cast'
  what():  std::bad_cast
Aborted (core dumped)

И с Visual Studio Express RC 2013:

First-chance exception at 0x76A6C41F in traits test.exe: Microsoft C++ exception: std::bad_cast at memory location 0x0038F978.
Unhandled exception at 0x76A6C41F in traits test.exe: Microsoft C++ exception: std::bad_cast at memory location 0x0038F978.

Изменение byte_type на char дает ожидаемый результат:

$ g++ -std=c++11 -Wall -Wextra -pedantic stream.cpp -o stream && ./stream /dev/random 
Got:4

Почему это бросание std:: bad_cast и как я могу его исправить?

Ответ 1

Я смог воспроизвести bad_cast на моем gcc (4.7.2 в AIX).

Причина в том, что разработчики gcc-библиотек оптимизировали basic_filebuf::xsgetn (который называется from basic_istream::read) для вызова plain C fread для чтения из файла, если локаль вашего потока не конвертируется (то есть вы "Не пытайтесь читать файл UTF-8 или, возможно, GB18030, в строку UTF-32 или что-то еще), что абсолютно правильно. Чтобы узнать, не является ли он конвертируемым, он проверяет codecvt:: always_noconv на грань codecvt языка, внедренного в ваш поток... который не существует.

Вы можете воспроизвести исключение, выполнив

std::cout << std::use_facet<
                     std::codecvt<std::uint8_t, char, std::mbstate_t>
             >(stream.getloc()).always_noconv() << '\n';

У меня нет доступа к Visual Studio, чтобы понять, почему он работает там (вызывают ли они только basic_filebuf::sgetc() для каждого char в basic_fstream::read()?), но для использования basic_filestream в любом случае вам необходимо предоставить грань codecvt для вашей комбинации внутренних и внешних типов (uint8_t и char, в данном случае).

РЕДАКТИРОВАТЬ: вы почти там, последний недостающий кусок - это строка

stream.imbue(std::locale(stream.getloc(), 
             new std::codecvt<uint8_t, char, std::mbstate_t>));

где-либо перед stream.read или, наоборот, введите global: std::locale::global(std::locale(std::locale(), new std::codecvt<uint8_t, char, std::mbstate_t>)); где-нибудь, прежде чем вы создадите basic_ifstream