C-обратный вызов для шаблона функции: явно создать шаблон - PullRequest
14 голосов
/ 18 июля 2011

Premise

Я использую библиотеку C (из C ++), которая обеспечивает следующий интерфейс:

void register_callback(void* f, void* data);
void invoke_callback();

Проблема

Теперь мне нужно зарегистрироватьШаблон функции в качестве обратного вызова, и это вызывает у меня проблемы.Рассмотрим следующий код:

template <typename T> void my_callback(void* data) { … }

int main() {
    int ft = 42;
    register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
    invoke_callback();
}

Это дает мне следующую ошибку компоновщика (при использовании g ++ (GCC) 4.5.1 на OS X , но работает на большинстве других комбинаций версии компилятора / платформы ):

Неопределенные символы для архитектуры x86_64:

"void my_callback<int>(void*)", referenced from:  
  _main in ccYLXc5w.o

, которые мне понятны.

Первое «решение»

Это легко исправить, явно создав экземпляр шаблона:

template void my_callback<int>(void* data);

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

template <typename T>
void do_register_callback(T& value) {
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
    // Other things …
}

int main() {
    int ft = 42;
    do_register_callback(ft);
    invoke_callback();
}

Второе «решение»

Шаблон функции неявно создается путем вызова функции.Так что давайте сделаем это, но убедитесь, что вызов на самом деле не выполняется (функция имеет побочные эффекты):

template <typename T>
void do_register_callback(T& value) {
    if (false) { my_callback<T>(0); }
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
}

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

Вопрос

Как создать экземпляр шаблона, для которого я не используюНе знаете аргументы шаблона? Этот вопрос явно бессмысленный: я не могу.- Но есть ли хитрый способ обойти это?

За исключением этого, гарантированно ли мой обходной путь будет успешным?

Бонусный вопрос

Код (конкретнотот факт, что я приведу указатель на функцию к void*), также вызывает следующее предупреждение:

ISO C ++ запрещает приведение между указателем на функцию и указателем на объект

при компиляции с -pedantic.Можно ли как-то избавиться от предупреждения без написания строго типизированной оболочки C для библиотеки (что невозможно в моей ситуации)?

Запуск кода на ideone (с добавленным приведением для его компиляции)

Ответы [ 3 ]

8 голосов
/ 18 июля 2011

Видимо, настоящей проблемой было отсутствие static_cast в моем исходном коде:

register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);

Это отлично компилирует , но вызывает ошибку liker при использовании GCC 4.5. Он даже не компилируется при использовании GCC 4.2, вместо этого выдается следующая ошибка компиляции:

недостаточно контекстной информации для определения типа

Как только эта «контекстная информация» предоставлена, код компилирует и ссылки :

register_callback(reinterpret_cast<void*>(
    static_cast<void(*)(void*)>(my_callback<int>)), &value);

Я понятия не имею, действительно ли приведение требуется и (если так), почему GCC 4.5 позволяет мне его отключить, а затем не удается создать экземпляр шаблона. Но по крайней мере я получил код для компиляции без использования хаков.

5 голосов
/ 18 июля 2011

Это должно работать:

template <typename T>
void do_register_callback(T& value) {
   void (*callback)(void*) = my_callback<T>;
   register_callback(reinterpret_cast<void*>(callback), &value);
}

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

РЕДАКТИРОВАТЬ: Позвольте мне добавить еще один вариант в этот микс. Сделайте my_callback статическим членом шаблона класса - что-то вроде следующего:

template <typename T>
struct foo
{
static void my_callback(void* data) {
    T& x = *static_cast<T*>(data);
    std:: cout << "Call[T] with " << x << std::endl;
}
};

Теперь, в вашем регистраторе, вам даже не нужен "актерский состав".

template <typename T>
void do_register_callback(T& value) {
   register_callback(reinterpret_cast<void*>(&foo<int>::my_callback), &value);
}

Похоже, что правило для создания экземпляров шаблонов классов отличается от шаблонов функций - т. Е. Для получения адреса члена класса тип имеет значение .

4 голосов
/ 18 июля 2011

POSIX рекомендует следующий способ приведения между типами указателей функций и типами указателей объектов (что не определено в C99):

typedef void function_type(void*);
function_type *p_to_function = &my_callback<T>;
void* p_to_data = *(void**)&p_to_function;

// Undefined:
// void* p_to_data = (void*)p_to_function;

Обратите внимание, что в C ++ - land это будет выполнять reinterpret_cast<void**>(&p_to_function) из function_type**. Это не неопределено, а определено реализацией, в отличие от reinterpret_cast<void*>(p_to_function). Так что, вероятно, вам лучше всего написать C ++ - совместимый код, основанный на реализации.

...