Различное поведение SFINAE для std :: tuple_size_v на разных компиляторах

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

#include <tuple>
#include <type_traits>
#include <iostream>

template <typename T, typename = void> struct is_tuple_like : std::false_type {};
template <typename T> struct is_tuple_like<T, decltype(std::tuple_size_v<T>, void())> : std::true_type {};

int main()
{
    std::cout << is_tuple_like<std::string>::value << '\n';
}

Выполнить на gcc.godbolt.org

В GCC 10.2 и MSVC 19.28 это вызывает серьезную ошибку, например:

error: incomplete type 'std::tuple_size<...>' used in nested name specifier

С другой стороны, в Clang 11.0.1 он компилирует и печатает 1 как с libstdc ++, так и с libc ++.

Какой компилятор здесь правильный?

Обратите внимание, что Clang печатает 1, а не 0, что означает, что он не рассматривает std::tuple_size<std::string>::value (инициализатор tuple_size_v) как программную ошибку, а вместо этого предпочитает полностью игнорировать ее!

В каком-то смысле это имеет смысл, поскольку если tuple_size_v определяется как template <typename T> inline constexpr size_t tuple_size_v = ..., тип decltype(tuple_size_v<...>) не зависит от параметра шаблона и всегда равен size_t.

Я предполагаю, что вопрос сводится к тому, требуется ли здесь создавать экземпляр инициализатора tuple_size_v, хотя это и не является строго необходимым.


Я знаю, что могу исправить это, заменив std::tuple_size_v<...> на std::tuple_size<...>::value, тогда он напечатает 0 во всех трех компиляторах.


person HolyBlackCat    schedule 15.02.2021    source источник
comment
Я думаю, что этот вопрос будет более пикантным, если вы включите кое-что из того, что мы обсуждали в моем удаленном ответе. К size_t или не к size_t можно сделать более явным.   -  person StoryTeller - Unslander Monica    schedule 15.02.2021
comment
@ StoryTeller-UnslanderMonica Согласен, отредактировано, чтобы добавить немного сока.   -  person HolyBlackCat    schedule 15.02.2021
comment
Кажется, что GCC по-разному обрабатывает шаблоны функций и шаблоны переменных. Бьюсь об заклад, это ошибка GCC (и MSVC).   -  person xskxzr    schedule 16.02.2021
comment
Кроме того, если мы удалим часть void, Clang выведет 0. Это странно.   -  person xskxzr    schedule 16.02.2021
comment
CWG2222?   -  person Language Lawyer    schedule 16.02.2021
comment
@LanguageLawyer Хм, хотя он ничего не говорит о шаблонах переменных.   -  person HolyBlackCat    schedule 16.02.2021
comment
@xskxzr Не очень странно, потому что decltype(...) теперь не void, поэтому специализация не совпадает и выбран первичный шаблон.   -  person HolyBlackCat    schedule 16.02.2021
comment
@HolyBlackCat Примечание. Я изменил аргумент шаблона по умолчанию на std::size_t.   -  person xskxzr    schedule 17.02.2021
comment
Возможный дубликат: stackoverflow.com/questions/49858074/ - но я понятия не имею, соответствует ли ответ вашим ожиданиям ...   -  person PiotrNycz    schedule 18.02.2021


Ответы (1)


Я думаю, что Кланг имеет на это право.

Правило из [temp.inst] / 7:

Если специализация шаблона переменной не является объявленной специализацией, специализация шаблона переменной неявно создается, когда на нее ссылаются в контексте, который требует существования определения переменной или если существование определения влияет на семантику программы.

Эта программа не требует определения std::tuple_size_v<std::string>, только объявление. И декларация:

template <typename T>
inline constexpr size_t tuple_size_v = tuple_size<T>::value;

Достаточно оценить выражение в частичной специализации. decltype(std::tuple_size_v<T>, void()) здесь вообще не зависит от значения, для любого size_t это допустимое выражение типа void.

Если мы имеем дело с шаблоном функции, а не с шаблоном переменной:

template <typename T>
constexpr size_t tuple_size_v() { return tuple_size<T>::value; }

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

Позже, в [temp.inst] / 8, у нас есть:

Считается, что наличие определения переменной или функции влияет на семантику программы, если переменная или функция необходимы для постоянной оценки выражением ([expr.const]), даже если постоянная оценка выражения не требуется. или если при вычислении константного выражения определение не используется.

Но здесь это не так: нам не нужна переменная для постоянной оценки.

person Barry    schedule 22.02.2021
comment
Интересно. В этом примере (предоставленном xskxzr в комментариях) Clangs не может сделать то же самое. - person HolyBlackCat; 22.02.2021
comment
@HolyBlackCat Да, это то же самое - вам нужен только decltype(tuple_size_v<T>), и вы знаете, что это size_t, даже не зная его ценности. - person Barry; 22.02.2021
comment
Есть одна вещь, в которой я не уверен: есть ли в стандарте концепция инсталляции объявлений шаблонов переменных отдельно от их определений? Я могу найти кое-что о создании экземпляров объявлений функций, но ничего о объявлениях переменных. - person HolyBlackCat; 24.02.2021