Вывод первого аргумента шаблона с другими параметрами шаблона по умолчанию

Gcc и clang, похоже, не согласны с тем, должен ли этот код компилироваться или нет:

#include <type_traits>

template <typename Signature, int N = 0>
struct MyDelegate { };

template <typename D>
struct signature_traits;

template <template <typename> class Delegate, typename Signature>
struct signature_traits<Delegate<Signature>>
{
    using type = Signature;
};

static_assert(std::is_same_v<
    void(int, int),
    signature_traits<MyDelegate<void(int, int)>>::type
>);

Посмотрите выход godbolt здесь и попробуйте. Я нахожусь здесь с clang, но что говорит об этом стандарт С++?

Следующий вопрос - можно ли это сделать, чтобы работать в clang?

Ответ 1

Это абсолютно правильный код, и gcc прав. "Особенность" была представленная в С++ 17. Это не особенность, потому что это отчет о дефектах. MyDelegate соответствует частичной специализации signature_traits, и поэтому его следует воспринимать как gcc правильно. Обратите внимание, что это работает, потому что второй параметр шаблона по умолчанию.

Причина, по которой clang не скомпилирует ее, состоит в том, что этот дефектный отчет имеет дефект: P. Он не вводит соответствующие изменения в частичном заказе, что не очень приятно и делает предыдущий действительный код неоднозначным снова.

Ожидается, что он будет исправлен в ближайшее время, но тем временем, clang решил "скрыть" функцию за флагом -frelaxed-template-template-args.

Итак, просто скомпилируйте этот флаг, и вы должны быть в порядке.

Ответ 2

Проблема заключается в том, что MyDelegate не соответствует template <typename> class, потому что принимает параметр два: тип (Signature) и a int (N).

Да: второе имеет значение по умолчанию. Но подпись остается template <typename, int> class.

So Я полагаю, что g++ ошибочен (компиляция без ошибок), а clang++ - это правильный. Rakete1111 исправил меня (спасибо!): Ваш код был неправильным до С++ 17, но правильно, начиная с С++ 17 (см. Его ответ на ссылки). Итак (вы компилируете С++ 17) g++ прав, а clang++ неверен.

Возможное решение (ожидающее правильного clang++) определяет signature_traits следующим образом

template <template <typename, int=0> class Delegate, typename Signature>
struct signature_traits<Delegate<Signature>>
{
    using type = Signature;
};

или, лучше IMHO, добавив целочисленный параметр

template <template <typename, int> class Delegate, typename Signature, int N>
struct signature_traits<Delegate<Signature, N>>
{
    using type = Signature;
};

Заметим, что оба решения совместимы с

static_assert(std::is_same<
    void(int, int),
    typename signature_traits<MyDelegate<void(int, int)>>::type
>::value);