C-обратный вызов к шаблону функции: явно создать экземпляр шаблона

помещение

Я использую библиотеку 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);

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

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 (с добавлением приведения для компиляции)


person Konrad Rudolph    schedule 18.07.2011    source источник
comment
кстати - if(false) является избыточным - так как static_cast все равно вызывает создание экземпляра.   -  person Nim    schedule 18.07.2011
comment
@Nim Это не избыточно в GCC 4.5, иначе у меня не было бы проблемы.   -  person Konrad Rudolph    schedule 18.07.2011
comment
В самом деле? следующее работает просто отлично - это 4.3.4: ideone.com/B6TTY   -  person Nim    schedule 18.07.2011
comment
Почему сигнатура обратного вызова не указана void (*)(void*) в обратном вызове регистра?   -  person David Rodríguez - dribeas    schedule 18.07.2011
comment
@ Дэвид Не спрашивай меня, я не имею ни малейшего представления. Это из сторонней библиотеки C.   -  person Konrad Rudolph    schedule 18.07.2011
comment
Почему бы не использовать if(false) { my_callback<T>(value); } вместо предположения, что все обратные вызовы будут использовать значения указателя? По крайней мере, это немного менее уродливо.   -  person Chris Lutz    schedule 18.07.2011
comment
@Chris: учитывая, что обратный вызов вызывается с помощью void*, я думаю, он всегда будет использовать аргумент-указатель.   -  person Matthieu M.    schedule 18.07.2011
comment
Кроме того, использование (void *) вместо reinterpret_cast<void *> может избавиться от предупреждения. Если это не так, вы всегда можете использовать союз.   -  person Chris Lutz    schedule 18.07.2011
comment
@Matthieu M. - Первоначально я думал, что это числовой аргумент, потому что я в основном использую C, где мы используем NULL вместо необработанного 0. Можно передать число как указатель, интерпретируя значение указателя как целое число, но поскольку OP показан код, передающий poInter в int, это кажется маловероятным. Ну да, все иногда ошибаются.   -  person Chris Lutz    schedule 18.07.2011
comment
@Chris Это не так, и я не понимаю, почему это должно быть - приведение в стиле C может быть только static_cast или reinterpret_cast (или что-то еще, что я забыл, но что здесь не применяется). Актерский состав еще исполняется.   -  person Konrad Rudolph    schedule 18.07.2011
comment
@Konrad - я признаю, что мне не хватает понимания приведения C ++ (я придерживаюсь C из-за предпочтения), но если компилятор предупреждает (правильно, как вы знаете) о фактическом cast указатель функции на указатель объекта, объединение кажется единственным способом заставить его замолчать (кроме настройки параметров вашего компилятора).   -  person Chris Lutz    schedule 18.07.2011
comment
@Chris Я полностью пропустил предложение использовать союз. Хороший звонок, это работает.   -  person Konrad Rudolph    schedule 18.07.2011


Ответы (3)


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

person Luc Danton    schedule 18.07.2011
comment
Таким образом, приведение указателя к указателю к функции должно быть разыменовано и передано в регистрацию обратного вызова? то есть *reinterpret_cast<void**>(&p_to_function) - и это определено реализацией? - person Nim; 18.07.2011
comment
..приведенное выше генерирует предупреждения о разыменовании указателя типа punned - person Nim; 18.07.2011
comment
@Nim Да, reinterpret_cast между несвязанными типами указателей определяется реализацией. - person Luc Danton; 18.07.2011
comment
должно быть поздно, я не понимаю разницы между этим дополнительным шагом через дополнительную косвенность и просто reinterpret_casting указатель функции на void* (это фактически приведение между двумя разными типами указателей - не так ли?) и поэтому должен быть определена реализация? - person Nim; 18.07.2011
comment
@Nim Когда я использую «тип указателя», он исключает «указатель на тип функции» из своего значения (кстати, анализируйте последний как тип (указатель на функцию)). Стандартная терминология на самом деле является «типом указателя объекта», «типом указателя функции» и «типом указателя члена». Я собираюсь изменить свой ответ. - person Luc Danton; 18.07.2011
comment
@Nim Я также нашел (и исправил) еще одну ошибку в своем ответе. - person Luc Danton; 18.07.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 позволяет мне отключить его, а затем не может создать экземпляр шаблона. Но, по крайней мере, я получил код для компиляции, не прибегая к хакам.

person Konrad Rudolph    schedule 18.07.2011
comment
Означает ли это, что у вас есть более одной перегрузки для шаблонной функции? Если есть один шаблон, не должно быть необходимости в дополнительной контекстной информации для определения типа (т. е. тип будет однозначно определен параметром типа в шаблоне). - person David Rodríguez - dribeas; 18.07.2011
comment
@ Дэвид, у меня нет. Код точно такой же, как в коде ideone.com. - person Konrad Rudolph; 18.07.2011
comment
@Konrad: теперь, когда вы упомянули об исправлении, я понимаю, что мне уже приходилось использовать static_cast в подобной ситуации. Я не зацикливался на этом... но так и не понял, ЗАЧЕМ это нужно. - person Matthieu M.; 18.07.2011
comment
.. так что последняя строка действительно работает на вашей комбинации платформы/компилятора? а то что я написал нет? интересный... - person Nim; 18.07.2011
comment
@Nim Позвольте мне переоценить это. Я думаю, вы правы, это фактически эквивалентно static_cast. У меня сейчас нет времени, но я сделаю это первым делом завтра. - person Konrad Rudolph; 18.07.2011
comment
Мне любопытно, если вы когда-нибудь поняли это, поскольку я столкнулся с той же проблемой. В clang поведение такое же, поэтому я предполагаю, что это не ошибка. Однако, похоже, что компилятор должен быть в состоянии понять это. Кроме того, кажется, что компилятору удается это выяснить, поскольку он вызывает ошибку компоновщика. Таким образом, возникает вопрос, почему неявное приведение к указателю на функцию не создает экземпляр шаблона функции, а явное static_cast делает? - person Robert Mason; 22.10.2012
comment
@Robert Хорошо, второй код работает. Но решение Люка (принятый ответ) лучше, потому что оно не полагается на неопределенное поведение. - person Konrad Rudolph; 22.10.2012
comment
Моя конкретная проблема не связана с приведением указателей функций к указателям данных - скорее, она связана с тем, что функция шаблона не создается. Итак, у меня то же самое, за исключением reinterpret_cast (функция регистрации принимает указатель на функцию), и без статического приведения там шаблон не инстанцируется. Компилятор принимает код без статического приведения без ошибок до времени компоновки, когда он не может найти функцию в .text - person Robert Mason; 22.10.2012
comment
@ Роберт Хм. Как я уже сказал в своем ответе, это действительно работает с static_cast. Если это по-прежнему вызывает у вас затруднения, попробуйте чат C++ Stack Overflow. Люди там, как правило, ворчат на вопросы, но делают исключения для интересных (таких как этот) и весьма находчивы. - person Konrad Rudolph; 22.10.2012

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

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);
}

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

person Nim    schedule 18.07.2011
comment
Я думаю, вы имеете в виду my_callback‹T› - person zennehoy; 18.07.2011
comment
Это звучит разумно. К сожалению, это определенно не работает с моей комбинацией компилятор/платформа. Для справки: ваш код также работает на моей платформе с другой версией компилятора (4.2) и работает с моей версией компилятора (4.5.1) на другой платформе. - person Konrad Rudolph; 18.07.2011
comment
@zennehoy, @Nim: кажется очевидным, я исправил. - person Matthieu M.; 18.07.2011