Перегрузка шаблонной функции не вызывается при рекурсивном вызове шаблона - PullRequest
2 голосов
/ 21 июня 2019

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

#include <cstdio>
#include <vector>

template<typename id_type>
struct B {
    id_type ID;
    std::vector<int> values;
};

template<typename id_type>
struct A {
    id_type ID;
    std::vector<struct B<id_type>> b_elems;
};

// forward declarations
namespace aSDG {
    namespace meshing {
        template<typename id_type> size_t byte_content(const struct B<id_type>& instance);
        template<typename id_type> size_t serialize(const struct B<id_type>& instance, unsigned char* buffer, size_t start_idx = 0);
        template<typename id_type> size_t deserialize(struct B<id_type>& instance, const unsigned char* buffer, size_t start_idx = 0);
        template<typename id_type> size_t byte_content(const struct A<id_type>& instance);
        template<typename id_type> size_t serialize(const struct A<id_type>& instance, unsigned char* buffer, size_t start_idx = 0);
        template<typename id_type> size_t deserialize(struct A<id_type>& instance, const unsigned char* buffer, size_t start_idx = 0);
    }
}

namespace aSDG {
    namespace meshing {

        // serialization for primitive types
        template<typename T> size_t byte_content(const T& data){
            return sizeof(T);
        }

        template<typename T> size_t serialize(const T& data, unsigned char* buffer, size_t start_idx = 0)
        {
            std::memcpy((void*)(buffer + start_idx), (void*)&data, sizeof(data));
            return start_idx + sizeof(data);
        }
        template<typename T> size_t deserialize(T& data, const unsigned char* buffer, size_t start_idx = 0)
        {
            std::memcpy((void*)&data, (void*)(buffer + start_idx), sizeof(data));
            return start_idx + sizeof(data);
        }

        // serialization for vector containers
        template<typename T> size_t byte_content(const std::vector<T>& data){

            // get number of bytes for the size variable
            size_t num_req_bytes = sizeof(size_t);

            // get the number of bytes for each element of the vector
            for(size_t i = 0; i < data.size(); ++i){
                num_req_bytes += byte_content(data[i]);
            }// end for i

            // return the total number of required bytes
            return num_req_bytes;
        }

        template<typename T> size_t serialize(const std::vector<T>& data, unsigned char* buffer, size_t start_idx = 0)
        {
            // add the number of elements in the data
            const size_t size_ = data.size();
            start_idx = serialize(size_, buffer, start_idx);

            // add the actual data elements
            for(size_t i = 0; i < size_; ++i){
                start_idx = serialize(data[i], buffer, start_idx);
            }// end for i

            // return the final index after adding all the data
            return start_idx;
        }

        template<typename T> size_t deserialize(std::vector<T>& data, const unsigned char* buffer, size_t start_idx = 0)
        {
            // get the number of elements in the array
            size_t size_ = 0;
            start_idx = deserialize(size_, buffer, start_idx);

            // resize the input array
            data.resize(size_);

            // fill the array with the data in the buffer
            for(size_t i = 0; i < size_; ++i){
                start_idx = deserialize(data[i], buffer, start_idx);
            }// end for i

            // return the number of bytes we are at in the array
            return start_idx;
        }

    } // end namespace meshing
} // end namespace aSDG

namespace aSDG {
    namespace meshing {

        // serialization for B
        template<typename id_type>
        size_t byte_content(const struct B<id_type>& instance) {
            return byte_content(instance.ID) + byte_content(instance.values);
        }

        template<typename id_type>
        size_t serialize(const struct B<id_type>& instance, unsigned char* buffer, size_t start_idx){
            start_idx = serialize(instance.ID, buffer, start_idx);
            return serialize(instance.values, buffer, start_idx);
        }

        template<typename id_type>
        size_t deserialize(struct B<id_type>& instance, const unsigned char* buffer, size_t start_idx){
            start_idx = deserialize(instance.ID, buffer, start_idx);
            return deserialize(instance.values, buffer, start_idx);
        }

        // serialization functions for A
        template<typename id_type>
        size_t byte_content(const struct A<id_type>& instance) {
            return byte_content(instance.ID) + byte_content(instance.b_elems);
        }

        template<typename id_type>
        size_t serialize(const struct A<id_type>& instance, unsigned char* buffer, size_t start_idx){
            start_idx = serialize(instance.ID, buffer, start_idx);
            return serialize(instance.b_elems, buffer, start_idx);
        }

        template<typename id_type>
        size_t deserialize(struct A<id_type>& instance, const unsigned char* buffer, size_t start_idx){
            start_idx = deserialize(instance.ID, buffer, start_idx);
            return deserialize(instance.b_elems, buffer, start_idx);
        }


    } // end namespace meshing
} // end namespace aSDG



int main(int argc, const char * argv[]) {

    struct A<size_t> a1, a2;
    a1.b_elems.emplace_back();
    a1.b_elems.emplace_back();
    a1.b_elems.emplace_back();
    a1.b_elems[0].ID = 5;
    a1.b_elems[0].values.push_back(1);

    // get the number of bytes to be serialized
    size_t num_req_bytes = aSDG::meshing::byte_content(a1);

    // allocate the buffer
    std::vector<unsigned char> buf( num_req_bytes );

    // serialize the data in a1
    size_t serial_bytes = aSDG::meshing::serialize(a1, &buf[0]);

    // deserialize data into a2
    size_t deserial_bytes= aSDG::meshing::deserialize(a2, &buf[0]);

    // check that the bytes match
    printf("in_bytes = %zu vs. out_bytes = %zu\n", serial_bytes, deserial_bytes );

    return 0;
}

В этом примере я иду к сериализации экземпляра типа A, и эта сериализация, в свою очередь, требует сериализации вектораB элементов, содержащихся в A.Все функции сериализации для A запускаются, то есть их разновидности byte_content, serialize и deserialize вызываются с соответствующими определениями.Однако, когда программа возвращается к общему определению std::vector этих методов для сериализации std::vector<struct B> члена данных A, она не вызывает методы, определенные для B, и вместо этого вызывает функции сериализации для базовыхпримитивы (первые три определены в верхней части примера кода).Я не могу понять, почему методы сериализации (byte_content, serialize, deserialize) для B не вызываются в этой ситуации, поскольку они определены.

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

Редактировать 1

Точнее говоря, ключевая проблема заключается в том, что когда происходит сериализация A, он на самом деле вызоветожидаемый метод ниже

template<typename id_type>
size_t aSDG::meshing::serialize(const struct A<id_type>& instance, unsigned char* buffer, size_t start_idx = 0){
    start_idx = serialize(instance.ID, buffer, start_idx);
    return serialize(instance.b_elems, buffer, start_idx);
}

Проблема заключается в том, что при сериализации b_elems сначала вызывается универсальный метод сериализации std::vector с T = struct B

template<typename T> size_t serialize(const std::vector<T>& data, unsigned char* buffer, size_t start_idx = 0)
{
    // add the number of elements in the data
    const size_t size_ = data.size();
    start_idx = serialize(size_, buffer, start_idx);

    // add the actual data elements
    for(size_t i = 0; i < size_; ++i){
        start_idx = serialize(data[i], buffer, start_idx);
    }// end for i

    // return the final index after adding all the data
    return start_idx;
}

но потом, когда она делает serialize(data[i], buffer, start_idx), функция не вызывает

template<typename id_type>
size_t serialize(const struct B<id_type>& instance, unsigned char* buffer, size_t start_idx = 0){
    start_idx = serialize(instance.ID, buffer, start_idx);
    return serialize(instance.values, buffer, start_idx);
}

, а вместо этого вызывает

template<typename T> size_t serialize(const T& data, unsigned char* buffer, size_t start_idx = 0)
{
    std::memcpy((void*)(buffer + start_idx), (void*)&data, sizeof(data));
    return start_idx + sizeof(data);
}

Я действительно не понимаю, почему это происходит.

Редактировать 2

После добавления предварительных объявлений, рекомендованных @Evg, код почти работает, как я и ожидал.Единственная проблема сейчас заключается в том, что специализация byte_content для B не вызывается.Это можно проверить, заменив приведенные выше определения специализации для B на

template<typename id_type>
size_t byte_content(const struct B<id_type>& instance) {
    printf("B byte_content\n");
    return byte_content(instance.ID) + byte_content(instance.values);
}

template<typename id_type>
size_t serialize(const struct B<id_type>& instance, unsigned char* buffer, size_t start_idx){
    printf("B serialize\n");
    start_idx = serialize(instance.ID, buffer, start_idx);
    return serialize(instance.values, buffer, start_idx);
}

template<typename id_type>
size_t deserialize(struct B<id_type>& instance, const unsigned char* buffer, size_t start_idx){
    printf("B deserialize\n");
    start_idx = deserialize(instance.ID, buffer, start_idx);
    return deserialize(instance.values, buffer, start_idx);
}

и убедившись, что сообщение "B byte_content" никогда не отображается.Теперь, может быть, я просто устал и не вижу какой-то ошибки, но я не понимаю, почему даже после предварительного объявления не вызывается правильная специализация byte_content для B.

1 Ответ

2 голосов
/ 21 июня 2019

Примечание: этот ответ относится к вопросу до его редактирования (без предварительных объявлений).

Внутри serialize(const std::vector<T>& data...) вы используете безусловное имя serialize. Компилятор должен решить, какой serialize вызвать. Будут рассмотрены функции 1), которые видны в точке определения, и 2) которые могут быть найдены ADL в точке создания. Оба поиска не смогут найти serialize(const B<id_type>&...).

Одним из возможных решений является выдвижение деклараций

template<typename id_type>
size_t byte_content(const B<id_type>&);

template<typename id_type>
size_t serialize(const B<id_type>&, unsigned char*, size_t = 0);

template<typename id_type>
size_t deserialize(B<id_type>&, const unsigned char*, size_t = 0);

в самом начале.

...