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

Относительно Функция передана как аргумент шаблона, ответ сообщества wiki, предоставленный Беном Супником, обсуждает проблему встраивания созданных шаблонов функций.

В этом ответе приведен следующий код:

template<typename OP>
int do_op(int a, int b, OP op)
{
  return op(a,b,);
}

int add(int a, b) { return a + b; }

int (* func_ptr)(int, int) = add;

int c = do_op(4,5,func_ptr);

В ответе говорится (в отношении последней строки, которая создает экземпляр шаблона функции do_op):

ясно, что это не делается.

Мой вопрос таков: почему ясно, что это не делается встраиванием?

Ответ 1

То, что он говорит (я думаю), заключается в том, что функция add не входит в состав. Другими словами, компилятор может встроить do_op следующим образом:

int c = func_ptr(4, 5);

но он не будет также inline add следующим образом:

int c = 4 + 5;

Однако он может ошибаться в этом простом примере.

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

void f1() { ... }
void f2() { ... }

void callThroughPointer() {
    int i = arc4random_uniform(2);
    void (*f)() = i ? f2 : f1;
    f();
}

Здесь компилятор не может знать, будет ли callThroughPointer вызывать f1 или f2, поэтому нет возможности встраивать либо f1, либо f2 в callThroughPointer.

Однако, если компилятор может во время компиляции доказать, какая функция будет вызываться, разрешается встроить функцию. Пример:

void f1() { ... }
void f2() { ... }

void callThroughPointer2() {
    int i = arc4random_uniform(2);
    void (*f)() = i ? f2 : f1;
    f = f1;
    f();
}

Здесь компилятор может доказать, что f всегда будет f1, поэтому ему разрешено встраивать f1 в callThroughPointer2. (Это не значит, что он будет inline f1...)

Аналогично, в примере, который вы цитировали в своем сообщении, компилятор может доказать, что func_ptr всегда add в вызове do_op, поэтому ему разрешено встроить add. (Это не значит, что он будет inline add...)

Ответ 2

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

int (* func_ptr)(int, int) = add;

указатель функции func_ptr может быть изменен, поэтому компилятор не гарантирует, что он никогда не изменится. В результате он не может включить вызов add.

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

Ответ 3

Почему ясно, что это не встраивается?

Это не так. Там нет причин, по которым компилятор не мог встроить весь код в этот фрагмент.