Параметры шаблона May 18, 2009

До чего людей доводит “тяга к прекрасному”…

Был вполне заурядный кусок кода:

namespace nmea
{
struct DefaultPolicy
{
    static bool IsAllowed(const char*, size_t)
    {
        return true;
    }
    // ....
};

template<typename Policy=DefaultPolicy>
struct NmeaProcessor
{
    bool Parse(IFieldReceiver* receiver)
    {
        // ....
        return true;
    }

    bool Accumulate(const char* data, size_t size)
    {
        // ....
        return true;
    }
};

} // namespace nmea

struct LocalPolicy
{
    static bool IsMessageAllowed(const char* data, size_t size)
    {
        return size >= 80 && !strncmp(data + 3, "TLL", sizeof "TLL" )
            && nmea_utils::IsCSGood(data);
    }
};

void foo(IDataSource* data_src, IFieldReceiver* receiver_ptr)
{
    nmea::NmeaProcessor<LocalPolicy> processor;

    while(IChunkProcessor::Instance()->ReadData(buf, size) )
    {
        processor.Accumulate(buf, size);
    }

    processor.Parse(receiver_ptr);
}

По эстетическим соображениям очень хотелось локальную политику определить там, где она используется, чтобы не размазывать код по всему файлу:

void foo(IDataSource* data_src, IFieldReceiver* receiver_ptr)
{
    struct LocalPolicy
    {
        static bool IsMessageAllowed(const char* data, size_t size)
        {
            return size >= 80 && !strncmp(data + 3, "TLL", sizeof "TLL" ) && nmea_utils::IsCSGood(data);
        }
    };

    nmea::NmeaProcessor<LocalPolicy> processor;

    while(IChunkProcessor::Instance()->ReadData(buf, size) )
    {
        processor.Accumulate(buf, size);
    }

    processor.Parse(receiver_ptr);
};

Результат получился вполне ожидаемый:

....\parser.cpp(121): error C2926: 'foo::LocalPolicy' : types with no linkage cannot be used as template arguments

Собственно, это не история, а только прелюдия к ней… Я подоспел уже к “шапочному” разбору, когда народ начал называть компилятор не очень приличными словами, поскольку он не позволяет написать то, что требуется. Ссылка на стандарт только подбавила масла в огонь, пришлось предложить “бытовую” гипотезу, доступную на “кухонном” уровне: - С++ обеспечивает типобезопасную компоновку - Следовательно, при инстанцировании шаблона пользовательским типом его сигнатура должна быть частью сигнатуры порожденного класса, чтобы обеспечить соблюдение ODR (one definition rule) - А вот для этого тип-параметр шаблона должен иметь эту самую сигнатуру, единую для всех единиц трансляции, то есть иметь ту самую внешнюю компоновку, что требуется стандартом (или что-то ей эквивалентное, но зачем?) - Разговоры о том, что при использовании в качестве параметра шаблона компоновка класса должна становиться внешней, пришлось оборвать, объяснив, что поведение любой системы должно быть консистентным - если, дописывая новый кусок кода, абсолютно не затрагивающий старый, мы можем изменить его семантику (например, при известном везении и совпадении имен) программа может перестать линковаться из-за внезапного появления нового класса в поле зрения компоновщика, то это есть “не айс”. Я не прав?

BTW, так что c “тягой к прекрасному” пришлось ограничиться внесением LocalPolicy в анонимный namespace :-)