Подтвердить что ты не робот

Разница между параметрами шаблона typename vs non typenames?

Пример кода 1:

namespace detail {
    enum enabler { dummy };
}

class foo {
public:
    template <typename T,
              typename std::enable_if<!std::is_integral<T>::value,
                                      detail::enabler>::type = detail::enabler::dummy>
    void func(T t) {
        std::cout << "other" << std::endl;
    }

    template <typename T,
              typename std::enable_if<std::is_integral<T>::value,
                                      detail::enabler>::type = detail::enabler::dummy>
    void func(T t) {
        std::cout << "integral" << std::endl;
    }
};

Пример кода 2:

namespace detail {
    enum enabler { dummy };
}

class foo {
public:
    template <typename T,
              typename T2 = typename std::enable_if<!std::is_integral<T>::value, detail::enabler>::type>
    void func(T t) {
        std::cout << "other" << std::endl;
    }

    template <typename T,
              typename T2 = typename std::enable_if<std::is_integral<T>::value, detail::enabler>::type>
    void func(T t) {
        std::cout << "integral" << std::endl;
    }
};

Я понимаю, почему образец 2 не компилируется. В основном потому, что обе функции шаблона похожи друг на друга (они даже имеют одинаковые параметры шаблона T, T2).

Я не понимаю, почему скомпилирован образец 1! Я вижу, что это та же проблема. В основном вместо того, чтобы иметь второй параметр шаблона как typename, на этот раз он enum вместо typename.

Может кто-нибудь объяснить, пожалуйста?

Далее я выполнил следующий фрагмент кода, и он вернул true, что еще более запутанно! (имеет смысл, что это правда, но запутывает, что скомпилирован образец 1)

std::cout
    << std::boolalpha
    << std::is_same<std::enable_if<std::is_integral<int>::value, int>::type,
                    std::enable_if<!std::is_integral<float>::value, int>::type>::value
    << std::endl;
4b9b3361

Ответ 1

Аргументы по умолчанию (как аргументы функции по умолчанию или аргументы шаблона по умолчанию) не являются частью сигнатуры функции. Вам разрешено определять только одну функцию с заданной подписью.

В примере 1 кода, если мы отбросим имена аргументов и все значения по умолчанию, у нас есть две функции:

template <typename T, std::enable_if_t<!std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }

template <typename T, std::enable_if_t<std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }

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

Теперь, что происходит, когда мы на самом деле называем это. Если T является интегральным типом, второй аргумент заменяется в обоих случаях:

template <typename T, ????>                    void func(T ) { ... }
template <typename T, detail::enabler = dummy> void func(T ) { ... }

В первой функции выражение typename std::enable_if<false, detail::enabler>::type плохо сформировано. В этом типе нет type typedef. Как правило, запись плохо сформированного кода является трудной ошибкой компилятора. Но, благодаря правилу, называемому SFINAE (Ошибка замещения не является ошибкой), плохо сформированный код, возникающий в непосредственном контексте замены шаблона, не является ошибкой - он просто заставляет исключить специализацию шаблона класса/класса из набора, Таким образом, мы получаем только один действительный:

template <typename T, detail::enabler = dummy> void func(T ) { ... }

который вызывается.


Однако в примере кода 2 мы имеем две функции:

template <typename T, typename>
void func(T ) { ... }

template <typename T, typename>
void func(T ) { ... }

Те же! Мы определяем одну и ту же функцию дважды - это недопустимо, следовательно, ошибка. Это ошибка по той же причине, что:

int foo(int x = 1) { return x; }
int foo(int x = 2) { return x; }

- ошибка.


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

Обратите внимание, что вы могли бы аналогичным образом сделать то же самое с чем-то вроде int:

template <class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
void func(T ) { ... }

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

Ответ 2

Думаю, я пришел к выводу (может быть, кто-нибудь мог проверить или обновить):

В примере 2:

Когда компилятор встречает вызов функции и пытается сопоставить его с шаблоном, он находит два шаблона с параметрами typename T и typename T2 (значения по умолчанию не имеют значения, это нормально), и это создает двусмысленность.

В примере 1:

Когда компилятор присоединяется к вызову функции, он попытается сопоставить его с одним из двух шаблонов, один из них будет успешным, поэтому он будет иметь typename T и detail::enabler, а другой не будет иметь значения, определенного для второго параметр шаблона, поэтому двусмысленность будет удалена в этом случае.