Почему функция шаблона только основывает возвращаемый тип, работает на C ++? - PullRequest
0 голосов
/ 15 января 2019

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

#include <iostream>
using namespace std;

template<typename T>
T add(double a, double b)
{
    return static_cast<T>(a + b); 
}

int main()
{
    cout << add<int>(1.1, 1) << endl;
    cout << add<double>(1.1, 1) << endl;
    return 0;
}

Сборка и запуск:

g++ -g -o test test.cpp
./test
2
2.1

Доза в стандарте C ++ проясняет это? Спасибо!

Ответы [ 5 ]

0 голосов
/ 18 января 2019

Я попробую использовать метод отсюда

Сначала укажите код теста:

template < class T> T add(T a, T b){
            return a+b;
}

void tmp(){
    add<int>(10, 2);
}

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

Затем введите общее:

gcc -S -O1 test.cpp

Наконец, я получу содержание ниже:

    .file   "compile2.cpp"
    .text
    .globl  _Z3tmpv
    .type   _Z3tmpv, @function
_Z3tmpv:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $2, %esi
    movl    $10, %edi
    call    _Z3addIiET_S0_S0_
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   _Z3tmpv, .-_Z3tmpv
    .globl  _Z3addii
    .type   _Z3addii, @function
_Z3addii:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %eax
    movl    -4(%rbp), %edx
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   _Z3addii, .-_Z3addii
    .section    .text._Z3addIiET_S0_S0_,"axG",@progbits,_Z3addIiET_S0_S0_,comdat
    .weak   _Z3addIiET_S0_S0_
    .type   _Z3addIiET_S0_S0_, @function
_Z3addIiET_S0_S0_:
.LFB3:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %eax
    movl    -4(%rbp), %edx
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE3:
    .size   _Z3addIiET_S0_S0_, .-_Z3addIiET_S0_S0_
    .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
    .section    .note.GNU-stack,"",@progbits

И, мы можем найти две различные функции подписи _Z3addii и _Z3addIiET_S0_S0_

[root@localhost template]# c++filt _Z3addIiET_S0_S0_
int add<int>(int, int)
[root@localhost template]# c++filt _Z3addii
add(int, int)
0 голосов
/ 15 января 2019

Пользователь StoryTeller дал лучший прямой ответ из standard. Я хотел бы подробнее остановиться на этом, приведя подробный пример того, как компиляторы относятся к этому:


Давайте посмотрим на ваш текущий код:

#include <iostream>
using namespace std;

template<typename T>
T add(double a, double b) {
    return static_cast<T>(a + b); 
}

int main() {
    cout << add<int>(1.1, 1) << endl;
    cout << add<double>(1.1, 1) << endl;
    return 0;
}

Посмотрим, как с этим справится компилятор. Прежде чем мы это сделаем, запомните следующее: templates необходимо знать во время компиляции и аналогично тому, как C ++ заменяет текст макросами и определяет, что он делает что-то подобное для templates также, когда они становятся экземплярами.

Ваш шаблон функции имеет такую ​​подпись: он будет генерировать функцию, которая ему когда-либо понадобится T.

template<typename T>
T add(double a, double b) {
    return static_cast<T>(a + b); 
}

Однако в этом случае T не является частью подписи. Подпись функции выглядит так:

::add<T>(double, double)

И так как templates argument относится к его типу return, а не к одному из его parameters, здесь он не действует.


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

int add( double, double );
float add( double, double );
double add( double, double );

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

#include <iostream>

int main() {
    std::cout << add( 1.1, 1 ) << '\n';  // <int> - reminder of original
    std::cout << add( 1.1, 1 ) << '\n';  // <double> -     ""
    return 0;
}

Теперь, глядя на код выше, у вас точно такой же вызов функции. Итак, какую перегрузку вызывает add в этом случае? Это довольно просто; без использования template и игнорирования ambiguity, вышеуказанная функция вызовет double add( double, double ).

Так как вышеприведенный вызовет ошибку компилятора из-за неоднозначности, давайте вернемся и применим template, чтобы выяснить, почему эта двусмысленность не возникает с версией template.


- Оригинальный код -

#include <iostream>

template<typename T>
T add( double a, double b ) {
    return static_cast<T>( a + b );
}

int main() {
    std::cout << add<int>(1.1, 1) << '\n';
    std::cout << add<double>(1.1,1) << '\n';
    return 0;
}

Давайте посмотрим, как компилятор обрабатывает это поэтапно:


-Шаг 1: - Разрешение имени, получение сигнатуры функции.

int main() {
    std::cout << ::add<int>( 1.1, 1 ) << '\n';
    std::cout << ::add<double>( 1.1, 1 ) << '\n';
    return 0;
}

-Шаг 2: - Вызов функции и создание стека вызовов функции

int main() {
    std::cout << 
        ::add<int>( 1.1, 1 ) {
           return static_cast<int>( 1.1 + 1 );
        }
              << '\n';

    std::cout <<
        ::add<double>( 1.1, 1 ) {
            return static_cast<double>( 1.1 + 1 );
        }
              << '\n';

    return 0;
}

-Шаг 3: - Выполнение всех инструкций в функции

int main() {
    std::cout << 
        /*::add<int>( 1.1, 1 ) {
           return static_cast<int>( 1.1 + 1 );
        }*/
           return static_cast<int>( 2.1 ); 
              << '\n';

    std::cout <<
        /*::add<double>( 1.1, 1 ) {
            return static_cast<double>( 1.1 + 1 );
        }*/
            return static_cast<double>( 2.1 );
              << '\n';
    return 0;
}

-Шаг 4: - Возвращение результата обратно из функции и очистка стека вызовов функции

int main() {
    std::cout << 
            return 2; 
              << '\n';

    std::cout <<
            return 2.1;
              << '\n';
    return 0;
}

-Step 5: - Основная функция передает возвращаемые результаты в операторы потока на стандартный вывод на экран.

int main() {
    std::cout << 2 << '\n';
    std::cout << 2.1 << '\n';
    return 0;
}

И это точно соответствует вашему выводу!

-Output-

2
2.1

Я надеюсь, что этот пробел поможет вам лучше понять templates и понять, почему здесь нет двусмысленности, как если бы вы их не использовали. Суть в том, что здесь нет двусмысленности из-за того, что вы explicitly создали экземпляры шаблонов функций.

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

0 голосов
/ 15 января 2019

Возможности шаблонов в C ++ развивались бессистемно с момента их первого появления (что, по сути, позволило чуть больше, чем позволить нам писать общие контейнерные классы). После этого сообщество программистов на C ++ вскоре применило их к другим видам использования (например, методам метапрограммирования).

Возможность создания экземпляров различных функций на основе одного только возвращаемого типа допускается, поскольку комитет по стандартам C ++ (фактически, сам Бьярне до передачи контроля над языком) счел это полезным. Это так: если бы только std::accumulate работал таким образом, а не выводил тип возвращаемого значения из типа переменной, предлагающей начальное значение!

Настолько полезный факт, что из C ++ 11 мы можем даже использовать синтаксис завершающего возвращаемого типа *1007*, чтобы компилятор мог получить возвращаемый тип, когда его можно обнаружить только путем проверки списка параметров функции и, позже, стандарты, содержание функции.

Обратите внимание на один миф, который нужно развенчать: в вашем случае add<double>(double, double) и add<int>(double, double) являются не перегрузками функций (как они могут быть? - имена, airity и типы параметров идентичны) , но это разные экземпляры функции шаблона.

0 голосов
/ 15 января 2019

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

[defns.signature]

имя-функции, список параметров-типов и окружающее пространство имен (если есть)

[Примечание: подписи используются в качестве основы для искажения имени и сшивание. - конец примечания]

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

[defns.signature.spec]

«специализация шаблона функции» подпись шаблона которой это специализация и ее аргументы шаблона (будь то явно указано или выведено)

Таким образом, для add<int>, int становится частью подписи. Не потому что это возвращаемый тип, а потому что это аргумент шаблона. То же самое для add<double>. И пока подписи разные, они могут быть идентифицированы как разные функции и, следовательно, могут быть перегружены на одно и то же имя.

0 голосов
/ 15 января 2019

Рассмотрим этот код:

int    foo(void) { return 1; }
double foo(void) { return 1.0; }

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

Но в вашем коде, когда вы вызываете add<int>(1.1, 1), компилятор видит только одного кандидата, поскольку вы явно указали параметр шаблона, который равен ::add<int>(double, double), поэтому здесь нет перегрузки и, следовательно, все в порядке.

Õ с другой стороны, следующий код вызовет ту же путаницу, что и первая часть ответа:

template int add<int>(double, double);
template double add<double>(double, double);

cout << add(1.1, 1);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...