Почему функции-члены нельзя использовать в качестве аргументов шаблона?

Почему функции-члены нельзя использовать в качестве аргументов шаблона? Например, я хочу сделать так:

struct Foo {
    void Bar() { // do something
    }
};
template <typename TOwner, void(&func)()>
void Call(TOwner *p) {
    p->func();
}
int main() {
    Foo a;
    Call<Foo, Foo::Bar>(&a);
    return 0;
}

Я знаю, что подобное можно сделать с помощью указателей на член; ну, в большинстве случаев это достаточно круто, но мне просто любопытно, почему указатели «должны» использоваться.

Я не вижу двусмысленности в интерпретации «p-> func ()» выше. Почему стандарт запрещает нам использовать функции-члены в качестве аргументов шаблона? Мой компилятор (VC++ 2013) не разрешает даже статические функции-члены. Кто-нибудь знает причину? Или есть способ сделать то же самое без потери производительности из-за разыменования указателя?

Спасибо.


person Junekey Jeon    schedule 19.06.2015    source источник
comment
Вы не видите двусмысленности, потому что думаете, что шаблоны похожи на макросы, но это не так, безопасность типов обеспечивается компилятором, и ваше выражение p->func() не имеет значения во время компиляции, а Foo::Bar относится не к типу void(&func)(), а к типу void(TOwner::*func)().   -  person Jean-Baptiste Yunès    schedule 19.06.2015
comment
@Jean-BaptisteYunès Ну, Foo::Bar не имеет типа void(TOwner::*func)(), который является типом &Foo::Bar. Причина, по которой я написал void(&func)(), заключается в том, что это лучшее, что я могу представить в качестве типа Foo::Bar; действительно, согласно моему компилятору, результатом decltype(Foo::Bar) является void(void). Я спрашиваю о потенциальной ситуации нарушения безопасности типов, когда разрешены такие вещи, как указано выше. Спасибо.   -  person Junekey Jeon    schedule 19.06.2015
comment
Хорошо для справки. Я просто хотел указать, что проблема в том, что между типом (какими параметрами и типом возвращаемого значения) и подписью существует некоторое различие? (контекст/пространство имен + тип). Увы, подпись Foo::Bar не является недействительной (недействительной).   -  person Jean-Baptiste Yunès    schedule 19.06.2015
comment
Дело в том, что Foo::Bar является членом функции, а аргумент вашего шаблона является функцией, но вы используете его как член функции.   -  person Jean-Baptiste Yunès    schedule 19.06.2015
comment
@ Jean-BaptisteYunès Да, и я хочу сказать, что, к сожалению, нет ни типа функции-члена, ни ссылки на тип члена, и поэтому я написал так, как указано выше.   -  person Junekey Jeon    schedule 20.06.2015


Ответы (2)


Их можно использовать как нетиповые параметры, но вам нужно использовать правильный синтаксис.

struct Foo {
    void Bar() { // do something
    }
};
template <typename TOwner, void(TOwner::*func)()>
void Call(TOwner *p) {
    (p->*func)();
}
int main() {
    Foo a;
    Call<Foo, &Foo::Bar>(&a);
    return 0;
}
person user657267    schedule 19.06.2015
comment
ОП сказал: я знаю, что подобное можно сделать с помощью указателей на член. - person songyuanyao; 19.06.2015
comment
@songyuanyao Если OP имел в виду, почему я не могу использовать ссылку на член в параметрах шаблона, то ответ таков, потому что такой вещи не существует. - person user657267; 19.06.2015
comment
Автоматические шаблоны C++17 не могут появиться достаточно скоро, чтобы исправить синтаксис вызова для этого, поэтому вам не нужен первый Foo в ‹Foo, &Foo::bar› - person xaxxon; 09.12.2016
comment
@ganesh нет указателя this в контексте main, я не понимаю вопроса. - person user657267; 06.12.2018
comment
Как мы можем изменить код, чтобы указатель на функцию-член был вторым параметром для вызова? - person Gupta; 15.03.2019

На самом деле указатели функций-членов могут использоваться в качестве аргументов шаблона (точно так же, как любой другой тип указателя может использоваться в качестве параметра шаблона):

struct A
{
    int f(float x);
};

template <int (A::F*)(float)>
struct B {};

template<A *> struct C;
template<A &> struct D;

Однако, согласно следующей выдержке из стандарта C++, нельзя передавать ссылки на члены.

[темп.парам]

  1. Параметр шаблона, не являющийся типом, должен иметь один из следующих (необязательно cv-квалифицированных) типов:

(4.1) — интегральный или нумерационный тип,

(4.2) — указатель на объект или указатель на функцию,

(4.3) — ссылка lvalue на объект или ссылка lvalue на функцию,

(4.4) — указатель на элемент,

(4.5) — std::nullptr_t.



Затем, если вам удалось каким-то образом передать тип вашей функции и вы хотите вызвать ее внутри, вы столкнетесь с той же проблемой, как если бы вы хотели сохранить их внутри указателя функции или объект std::function: а именно для вызова вам нужны как функция-член, так и конкретный объект. Передачи только функции будет недостаточно.

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

template<typename T, typename F>
void call(T&& t, F&&f)
{
    f(std::forward<T>(t));
}

struct A
{
    void foo() { std::cout<<"hello"<<std::endl; }  
};

int main()
{
    A a;
    auto f=std::bind(&A::foo, a);   //or possibly "std::ref(a)" instead of "a"
    call(3,f);
}

ДЕМО

person davidhigh    schedule 19.06.2015
comment
Ссылок на участников не существует. - person user657267; 19.06.2015
comment
@user657267: правильно, поэтому вы не можете их использовать как параметры шаблона :). Серьезно, спасибо, что заметили. - person davidhigh; 19.06.2015
comment
Спасибо за любезное объяснение. Как вы продемонстрировали, я согласен с тем, что std::bind — почти идеальное решение, но я думаю, что это не сильно отличается от использования указателя на член; то есть, если я это сделаю, должны быть добавлены некоторые динамические накладные расходы. Конечно, конкретный объект должен связываться с вызываемой функцией-членом, но это не относится к статическим членам. Что вы думаете об этом? Кроме того, не думаете ли вы, что p-› предоставляет такой конкретный объект для привязки? Я не могу представить себе ситуацию, когда представление func() как функции-члена p может вызвать проблему. - person Junekey Jeon; 19.06.2015
comment
@JunekeyJeon: во-первых: да, p-> - это требуемый объект, я пропустил это в вашем коде. Итак, как указано в другом ответе, это просто синтаксическая ошибка. - person davidhigh; 19.06.2015
comment
@JunekeyJeon: далее, я думаю, что ни в одном из двух случаев не используются динамические накладные расходы, потому что в обоих случаях компилятор знает, какую функцию он должен вызывать. Однако могут возникнуть накладные расходы, если вы используете это вместе с полиморфизмом, т.е. передаете указатель базового класса и вызываете виртуальную функцию (но это более общая вещь и не имеет прямого отношения к вашему вопросу). Что касается статического члена: это должно работать, да, но я не знаю, как... Я стараюсь избегать всех этих уродливых указателей на функции и работать с современными альтернативами С++ 11. - person davidhigh; 19.06.2015
comment
@davidhigh Динамические накладные расходы, вызванные вызовом виртуальных функций, меня не беспокоят. Причина, по которой я думал, что std::bind или указатель на функцию могут вызвать динамические накладные расходы, заключается в том, что они являются указателями. При обычном использовании указателей (т. е. не в качестве параметра шаблона, а в качестве обычных переменных) обращение к адресу, на который они указывают, должно создавать динамические накладные расходы. Но чтобы использоваться в качестве параметров шаблона, указатель должен быть константой времени компиляции, и я думаю, что это может быть причиной того, что вообще нет накладных расходов. Это правильно? - person Junekey Jeon; 20.06.2015