Шаблонная функция преобразования в указатель на функцию

Ура, еще один вопрос, состоящий из случайной последовательности терминов C ++!

Обычно мы делаем класс Callable, реализуя operator(). Но вы также можете сделать это, реализовав определяемое пользователем преобразование в указатель на функцию или ссылочный тип. Вместо использования идеальной пересылки функция преобразования может возвращать указатель на функцию, которая затем вызывается с исходным списком аргументов.

struct call_printf {
    typedef int printf_t( char const *, ... );
    operator printf_t & () { return std::printf; }
};

http://ideone.com/kqrJz

Насколько я могу судить, typedef выше является синтаксической необходимостью. Имя функции преобразования формируется из type-specificier-seq, что не позволяет использовать такие конструкции, как int (*)(). Для этого потребуется abstract-declarator. По-видимому, причина в том, что такие имена типов усложняются, а сложные конструкции, используемые в качестве имен объектов, трудно анализировать.

Функции преобразования также могут быть шаблонными, но аргументы шаблона должны быть выведены, потому что их негде явно указать. (Это лишило бы смысла неявное преобразование.)


Вопрос №1: В C ++ 03 не было возможности указать шаблон оператора преобразования функции? Похоже, что не было способа разрешить аргументы шаблона (то есть назвать их в выведенном контексте) в приемлемый тип указателя функции.

Вот эквивалентная ссылка из C ++ 11, §13.3.1.1.2 / 2 [over.call.object]. По сути, это то же самое, что и в C ++ 03:

Кроме того, для каждой неявной функции преобразования, объявленной в T формы

operator conversion-type-id () cv-qualifier attribute-specifier-seqopt;

где cv-qualifier - это то же cv-квалификация, что и, или большее cv-qualification, чем, cv, и где conversion-type-id < / em> обозначает тип «указатель на функцию (P1, ..., Pn), возвращающую R» или тип «ссылка на указатель на функцию (P1, ..., Pn), возвращающую R» , или тип «ссылка на функцию (P1, ..., Pn), возвращающую R», суррогатная функция вызова с уникальным именем call-function и имеющая форму

R call-function ( conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1,... ,an); }

также рассматривается как функция-кандидат. Точно так же суррогатные функции вызова добавляются к набору функций-кандидатов для каждой неявной функции преобразования, объявленной в базовом классе T, при условии, что функция не скрыта внутри T другим промежуточным объявлением.


Вопрос № 2: Можно ли в C ++ 11 указать такое преобразование с помощью аргумента шаблона по умолчанию? Это полезно для SFINAE. Единственное отличие здесь от приведенного выше примера состоит в том, что идентификатор-типа-преобразования представляет только ссылку на функцию после создания экземпляра, поскольку это зависимый тип (несмотря на неизменность). Это запускает GCC и пропускает шаблон участника.

enum { call_alternate = true; }

struct call_switch {
    template< bool en = call_alternate >
    operator typename std::enable_if< en, decltype( fn_1 ) & >::type ()
        { return fn_1; }

    template< bool en = ! call_alternate >
    operator typename std::enable_if< en, decltype( fn_2 ) & >::type ()
        { return fn_2; }
};

У нас также есть шаблоны псевдонимов. Кажется, что подстановка псевдонима происходит до создания экземпляра, учитывая пример в §14.5.7 / 2, где объявления process конфликтуют. В GCC 4.7 этот код, по крайней мере, создает экземпляр объявления, но затем выдает причудливую ошибку «кандидат ожидает 2 аргумента, 2 предоставлено».

template< typename t >
using fn_t = void (&)( t );

struct talk {
    template< typename t >
    operator fn_t< t >() { return fn; }
};

int main() {
    talk()( 3 );
}

person Potatoswatter    schedule 13.06.2012    source источник
comment
Что ты пытаешься сделать? Где это может быть полезно?   -  person Nawaz    schedule 13.06.2012
comment
@Nawaz, если вам нужен оператор преобразования в типы указателей функций?   -  person R. Martinho Fernandes    schedule 13.06.2012
comment
@Nawaz Первый пример под вопросом 2 - как я попал в это. Функция преобразования кажется лучше, чем идеальная пересылка. Когда я проверил исходный код GCC, чтобы понять, почему он не работает, я обнаружил функцию add_template_conv_candidate, которая кажется недоступной из-за синтаксических особенностей. Итак, я хочу понять этот темный уголок языка.   -  person Potatoswatter    schedule 13.06.2012
comment
Зарегистрированная ошибка GCC: gcc.gnu.org/bugzilla/show_bug.cgi?id= 53660   -  person Potatoswatter    schedule 13.06.2012


Ответы (1)


Вопрос №1: В C ++ 03 не было возможности указать шаблон оператора преобразования функции? Похоже, что не было способа разрешить аргументы шаблона (то есть назвать их в выведенном контексте) в приемлемый тип указателя функции.

Да это верно.

Вопрос №2: можно ли в C ++ 11 указать такое преобразование с помощью аргумента шаблона по умолчанию?

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

У нас также есть шаблоны псевдонимов. Кажется, что подстановка псевдонима происходит до создания экземпляра, учитывая пример в §14.5.7 / 2, где объявления процесса конфликтуют. В GCC 4.7 этот код, по крайней мере, создает экземпляр объявления, но затем выдает причудливую ошибку «кандидат ожидает 2 аргумента, 2 предоставлено».

Да, это https://groups.google.com/forum/?fromgroups#!topic/comp.std.c++/lXLFBcF_m3c (и вызвал закрытие DR395), но даже если такой шаблон функции преобразования может работать в таких случаях, как void(&p)() = yourClassObject, он не будет работать для суррогата функции вызова, потому что там функция преобразования должна предоставлять фиксированный независимый тип, в который объект класса преобразуется при вызове суррогатной функции, но шаблон функции преобразования обычно не предоставляет такой тип (странные вещи, такие как template<typename = int> operator Identity<void(*)()>(); в сторону. ..).

Я думаю, что GCC может неправильно сгенерировать кандидата call-function(void (&)( t ), t) с зависимыми типами, все еще существующими, и попытаться вызвать этого кандидата, тем самым нарушив некоторый его инвариант (что могло бы объяснить странное сообщение об ошибке - возможно, где-то неожиданно ударил } else { ... }).

person Johannes Schaub - litb    schedule 13.06.2012
comment
Отличный ответ, за исключением того, что должно быть ясно, почему? Функция преобразования формулировок, объявленная в T в форме operator идентификатор-типа-преобразования (), ничего не говорит о том, что она не является шаблоном. - person Potatoswatter; 14.06.2012
comment
@Potatoswatter, я думаю, это упущение в спецификации. Но мне просто нет смысла рассматривать здесь шаблоны функций преобразования. Какой должна быть семантика? - person Johannes Schaub - litb; 14.06.2012
comment
Пока тип функции можно определить из списка аргументов с помощью разрешения перегрузки, никакой специальной семантики не требуется. Это именно то, что делает GCC (на полпути). По сути, суррогатная функция вызова становится шаблоном суррогатной функции вызова. - person Potatoswatter; 14.06.2012
comment
@Potatoswatter всегда будет сбой при дедукции, потому что первый параметр всегда будет не совпадать. В вашем случае void(&)(t) не соответствует типу talk (подразумеваемый аргумент объекта). Аргументом от комитета, вероятно, будет функция преобразования. Шаблон функции преобразования не является функцией преобразования .. Хотя более четкий текст из спецификации, вероятно, был бы лучше. - person Johannes Schaub - litb; 14.06.2012
comment
Возможно. Конечно, я имею в виду, что к шаблону применяется то же преобразование, что и к функции. В настоящем суррогате нет необходимости, все как если бы. - person Potatoswatter; 14.06.2012